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.
- agentfield/__init__.py +66 -0
- agentfield/agent.py +3569 -0
- agentfield/agent_ai.py +1125 -0
- agentfield/agent_cli.py +386 -0
- agentfield/agent_field_handler.py +494 -0
- agentfield/agent_mcp.py +534 -0
- agentfield/agent_registry.py +29 -0
- agentfield/agent_server.py +1185 -0
- agentfield/agent_utils.py +269 -0
- agentfield/agent_workflow.py +323 -0
- agentfield/async_config.py +278 -0
- agentfield/async_execution_manager.py +1227 -0
- agentfield/client.py +1447 -0
- agentfield/connection_manager.py +280 -0
- agentfield/decorators.py +527 -0
- agentfield/did_manager.py +337 -0
- agentfield/dynamic_skills.py +304 -0
- agentfield/execution_context.py +255 -0
- agentfield/execution_state.py +453 -0
- agentfield/http_connection_manager.py +429 -0
- agentfield/litellm_adapters.py +140 -0
- agentfield/logger.py +249 -0
- agentfield/mcp_client.py +204 -0
- agentfield/mcp_manager.py +340 -0
- agentfield/mcp_stdio_bridge.py +550 -0
- agentfield/memory.py +723 -0
- agentfield/memory_events.py +489 -0
- agentfield/multimodal.py +173 -0
- agentfield/multimodal_response.py +403 -0
- agentfield/pydantic_utils.py +227 -0
- agentfield/rate_limiter.py +280 -0
- agentfield/result_cache.py +441 -0
- agentfield/router.py +190 -0
- agentfield/status.py +70 -0
- agentfield/types.py +710 -0
- agentfield/utils.py +26 -0
- agentfield/vc_generator.py +464 -0
- agentfield/vision.py +198 -0
- agentfield-0.1.22rc2.dist-info/METADATA +102 -0
- agentfield-0.1.22rc2.dist-info/RECORD +42 -0
- agentfield-0.1.22rc2.dist-info/WHEEL +5 -0
- agentfield-0.1.22rc2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import os
|
|
3
|
+
import signal
|
|
4
|
+
import threading
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
|
|
7
|
+
import requests
|
|
8
|
+
from agentfield.types import AgentStatus, HeartbeatData
|
|
9
|
+
from agentfield.logger import (
|
|
10
|
+
log_heartbeat,
|
|
11
|
+
log_debug,
|
|
12
|
+
log_warn,
|
|
13
|
+
log_error,
|
|
14
|
+
log_success,
|
|
15
|
+
log_setup,
|
|
16
|
+
log_info,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class AgentFieldHandler:
|
|
21
|
+
"""
|
|
22
|
+
AgentField Server Communication handler for Agent class.
|
|
23
|
+
|
|
24
|
+
This class encapsulates all AgentField server communication functionality including:
|
|
25
|
+
- Agent registration with AgentField server
|
|
26
|
+
- Heartbeat management (both simple and enhanced)
|
|
27
|
+
- Fast lifecycle management
|
|
28
|
+
- Graceful shutdown notifications
|
|
29
|
+
- Signal handling for fast shutdown
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(self, agent_instance):
|
|
33
|
+
"""
|
|
34
|
+
Initialize the AgentField handler with a reference to the agent instance.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
agent_instance: The Agent instance this handler belongs to
|
|
38
|
+
"""
|
|
39
|
+
self.agent = agent_instance
|
|
40
|
+
|
|
41
|
+
async def register_with_agentfield_server(self, port: int):
|
|
42
|
+
"""Register this agent node with AgentField server"""
|
|
43
|
+
# Import the callback URL resolution function
|
|
44
|
+
from agentfield.agent import (
|
|
45
|
+
_build_callback_candidates,
|
|
46
|
+
_resolve_callback_url,
|
|
47
|
+
_is_running_in_container,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
# Enhanced debugging for callback URL resolution
|
|
51
|
+
log_debug("Starting callback URL resolution")
|
|
52
|
+
log_debug(f"Original callback_url parameter: {self.agent.callback_url}")
|
|
53
|
+
log_debug(
|
|
54
|
+
f"AGENT_CALLBACK_URL env var: {os.environ.get('AGENT_CALLBACK_URL', 'NOT_SET')}"
|
|
55
|
+
)
|
|
56
|
+
log_debug(f"Port: {port}")
|
|
57
|
+
log_debug(f"Running in container: {_is_running_in_container()}")
|
|
58
|
+
log_debug(
|
|
59
|
+
f"All env vars containing 'AGENT': {[k for k in os.environ.keys() if 'AGENT' in k.upper()]}"
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# 🔥 FIX: Only resolve callback URL if not already set
|
|
63
|
+
# This prevents overwriting the URL resolved in Agent.__init__()
|
|
64
|
+
if not self.agent.base_url:
|
|
65
|
+
self.agent.callback_candidates = _build_callback_candidates(
|
|
66
|
+
self.agent.callback_url, port
|
|
67
|
+
)
|
|
68
|
+
if self.agent.callback_candidates:
|
|
69
|
+
self.agent.base_url = self.agent.callback_candidates[0]
|
|
70
|
+
log_debug(
|
|
71
|
+
f"Resolved callback URL during registration: {self.agent.base_url}"
|
|
72
|
+
)
|
|
73
|
+
else:
|
|
74
|
+
self.agent.base_url = _resolve_callback_url(
|
|
75
|
+
self.agent.callback_url, port
|
|
76
|
+
)
|
|
77
|
+
log_debug(
|
|
78
|
+
f"Resolved callback URL during registration: {self.agent.base_url}"
|
|
79
|
+
)
|
|
80
|
+
else:
|
|
81
|
+
# Update port in existing base_url if needed, but preserve Railway internal URLs
|
|
82
|
+
import urllib.parse
|
|
83
|
+
|
|
84
|
+
parsed = urllib.parse.urlparse(self.agent.base_url)
|
|
85
|
+
|
|
86
|
+
# Don't modify Railway internal URLs or other container-specific URLs
|
|
87
|
+
if "railway.internal" in parsed.netloc or "internal" in parsed.netloc:
|
|
88
|
+
log_debug(
|
|
89
|
+
f"Preserving container-specific callback URL: {self.agent.base_url}"
|
|
90
|
+
)
|
|
91
|
+
elif parsed.port != port:
|
|
92
|
+
# Update the port in the existing URL
|
|
93
|
+
self.agent.base_url = f"{parsed.scheme}://{parsed.hostname}:{port}"
|
|
94
|
+
log_debug(
|
|
95
|
+
f"Updated port in existing callback URL: {self.agent.base_url}"
|
|
96
|
+
)
|
|
97
|
+
else:
|
|
98
|
+
log_debug(f"Using existing callback URL: {self.agent.base_url}")
|
|
99
|
+
|
|
100
|
+
if not self.agent.callback_candidates:
|
|
101
|
+
self.agent.callback_candidates = _build_callback_candidates(
|
|
102
|
+
self.agent.base_url, port
|
|
103
|
+
)
|
|
104
|
+
elif (
|
|
105
|
+
self.agent.base_url
|
|
106
|
+
and self.agent.callback_candidates[0] != self.agent.base_url
|
|
107
|
+
):
|
|
108
|
+
# Keep resolved base URL at front for clarity
|
|
109
|
+
if self.agent.base_url in self.agent.callback_candidates:
|
|
110
|
+
self.agent.callback_candidates.remove(self.agent.base_url)
|
|
111
|
+
self.agent.callback_candidates.insert(0, self.agent.base_url)
|
|
112
|
+
|
|
113
|
+
# Always log the resolved callback URL for debugging
|
|
114
|
+
log_info(f"Final callback URL: {self.agent.base_url}")
|
|
115
|
+
|
|
116
|
+
if self.agent.dev_mode:
|
|
117
|
+
log_debug(f"Final callback URL: {self.agent.base_url}")
|
|
118
|
+
|
|
119
|
+
try:
|
|
120
|
+
log_debug(
|
|
121
|
+
f"Attempting to register with AgentField server at {self.agent.agentfield_server}"
|
|
122
|
+
)
|
|
123
|
+
discovery_payload = self.agent._build_callback_discovery_payload()
|
|
124
|
+
|
|
125
|
+
success, payload = await self.agent.client.register_agent(
|
|
126
|
+
node_id=self.agent.node_id,
|
|
127
|
+
reasoners=self.agent.reasoners,
|
|
128
|
+
skills=self.agent.skills,
|
|
129
|
+
base_url=self.agent.base_url,
|
|
130
|
+
discovery=discovery_payload,
|
|
131
|
+
vc_metadata=self.agent._build_vc_metadata(),
|
|
132
|
+
)
|
|
133
|
+
if success:
|
|
134
|
+
if payload:
|
|
135
|
+
self.agent._apply_discovery_response(payload)
|
|
136
|
+
log_success(
|
|
137
|
+
f"Registered node '{self.agent.node_id}' with AgentField server"
|
|
138
|
+
)
|
|
139
|
+
self.agent.agentfield_connected = True
|
|
140
|
+
|
|
141
|
+
# Attempt DID registration after successful AgentField registration
|
|
142
|
+
if self.agent.did_manager:
|
|
143
|
+
did_success = self.agent._register_agent_with_did()
|
|
144
|
+
if not did_success and self.agent.dev_mode:
|
|
145
|
+
log_warn(
|
|
146
|
+
"DID registration failed, continuing without DID functionality"
|
|
147
|
+
)
|
|
148
|
+
else:
|
|
149
|
+
log_error("Registration failed")
|
|
150
|
+
self.agent.agentfield_connected = False
|
|
151
|
+
|
|
152
|
+
except Exception as e:
|
|
153
|
+
self.agent.agentfield_connected = False
|
|
154
|
+
if self.agent.dev_mode:
|
|
155
|
+
log_warn(f"AgentField server not available: {e}")
|
|
156
|
+
log_setup("Running in development mode - agent will work standalone")
|
|
157
|
+
log_info(
|
|
158
|
+
f"To connect to AgentField server, start it at {self.agent.agentfield_server}"
|
|
159
|
+
)
|
|
160
|
+
else:
|
|
161
|
+
log_error(f"Failed to register with AgentField server: {e}")
|
|
162
|
+
if (
|
|
163
|
+
isinstance(e, requests.exceptions.RequestException)
|
|
164
|
+
and e.response is not None
|
|
165
|
+
):
|
|
166
|
+
log_warn(f"Response status: {e.response.status_code}")
|
|
167
|
+
log_warn(f"Response text: {e.response.text}")
|
|
168
|
+
raise
|
|
169
|
+
|
|
170
|
+
def send_heartbeat(self):
|
|
171
|
+
"""Send heartbeat to AgentField server"""
|
|
172
|
+
if not self.agent.agentfield_connected:
|
|
173
|
+
return # Skip heartbeat if not connected to AgentField
|
|
174
|
+
|
|
175
|
+
try:
|
|
176
|
+
headers = {"Content-Type": "application/json"}
|
|
177
|
+
if self.agent.api_key:
|
|
178
|
+
headers["X-API-Key"] = self.agent.api_key
|
|
179
|
+
response = requests.post(
|
|
180
|
+
f"{self.agent.agentfield_server}/api/v1/nodes/{self.agent.node_id}/heartbeat",
|
|
181
|
+
headers=headers,
|
|
182
|
+
timeout=5,
|
|
183
|
+
)
|
|
184
|
+
if response.status_code == 200:
|
|
185
|
+
log_heartbeat("Heartbeat sent successfully")
|
|
186
|
+
else:
|
|
187
|
+
log_warn(
|
|
188
|
+
f"Heartbeat failed with status {response.status_code}: {response.text}"
|
|
189
|
+
)
|
|
190
|
+
except Exception as e:
|
|
191
|
+
log_error(f"Failed to send heartbeat: {e}")
|
|
192
|
+
|
|
193
|
+
def heartbeat_worker(
|
|
194
|
+
self, interval: int = 30
|
|
195
|
+
): # pragma: no cover - long-running thread loop
|
|
196
|
+
"""Background worker that sends periodic heartbeats"""
|
|
197
|
+
if not self.agent.agentfield_connected:
|
|
198
|
+
log_heartbeat(
|
|
199
|
+
"Heartbeat worker skipped - not connected to AgentField server"
|
|
200
|
+
)
|
|
201
|
+
return
|
|
202
|
+
|
|
203
|
+
log_heartbeat(f"Starting heartbeat worker (interval: {interval}s)")
|
|
204
|
+
while not self.agent._heartbeat_stop_event.wait(interval):
|
|
205
|
+
self.send_heartbeat()
|
|
206
|
+
log_heartbeat("Heartbeat worker stopped")
|
|
207
|
+
|
|
208
|
+
def start_heartbeat(self, interval: int = 30):
|
|
209
|
+
"""Start the heartbeat background thread"""
|
|
210
|
+
if not self.agent.agentfield_connected:
|
|
211
|
+
return # Skip heartbeat if not connected to AgentField
|
|
212
|
+
|
|
213
|
+
if (
|
|
214
|
+
self.agent._heartbeat_thread is None
|
|
215
|
+
or not self.agent._heartbeat_thread.is_alive()
|
|
216
|
+
):
|
|
217
|
+
self.agent._heartbeat_stop_event.clear()
|
|
218
|
+
self.agent._heartbeat_thread = threading.Thread(
|
|
219
|
+
target=self.heartbeat_worker, args=(interval,), daemon=True
|
|
220
|
+
)
|
|
221
|
+
self.agent._heartbeat_thread.start()
|
|
222
|
+
|
|
223
|
+
def stop_heartbeat(self):
|
|
224
|
+
"""Stop the heartbeat background thread"""
|
|
225
|
+
if self.agent._heartbeat_thread and self.agent._heartbeat_thread.is_alive():
|
|
226
|
+
log_debug("Stopping heartbeat worker...")
|
|
227
|
+
self.agent._heartbeat_stop_event.set()
|
|
228
|
+
self.agent._heartbeat_thread.join(timeout=5)
|
|
229
|
+
|
|
230
|
+
async def send_enhanced_heartbeat(self) -> bool:
|
|
231
|
+
"""
|
|
232
|
+
Send enhanced heartbeat with current status and MCP information.
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
True if heartbeat was successful, False otherwise
|
|
236
|
+
"""
|
|
237
|
+
if not self.agent.agentfield_connected:
|
|
238
|
+
return False
|
|
239
|
+
|
|
240
|
+
try:
|
|
241
|
+
# Get MCP server health information
|
|
242
|
+
mcp_servers = self.agent.mcp_handler._get_mcp_server_health()
|
|
243
|
+
|
|
244
|
+
# Create heartbeat data
|
|
245
|
+
heartbeat_data = HeartbeatData(
|
|
246
|
+
status=self.agent._current_status,
|
|
247
|
+
mcp_servers=mcp_servers,
|
|
248
|
+
timestamp=datetime.now().isoformat(),
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
# Send enhanced heartbeat
|
|
252
|
+
success = await self.agent.client.send_enhanced_heartbeat(
|
|
253
|
+
self.agent.node_id, heartbeat_data
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
if success:
|
|
257
|
+
log_heartbeat(
|
|
258
|
+
f"Enhanced heartbeat sent - Status: {self.agent._current_status.value}"
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
return success
|
|
262
|
+
|
|
263
|
+
except Exception as e:
|
|
264
|
+
if self.agent.dev_mode:
|
|
265
|
+
log_error(f"Enhanced heartbeat failed: {e}")
|
|
266
|
+
return False
|
|
267
|
+
|
|
268
|
+
async def notify_shutdown(self) -> bool:
|
|
269
|
+
"""
|
|
270
|
+
Notify AgentField server of graceful shutdown.
|
|
271
|
+
|
|
272
|
+
Returns:
|
|
273
|
+
True if notification was successful, False otherwise
|
|
274
|
+
"""
|
|
275
|
+
if not self.agent.agentfield_connected:
|
|
276
|
+
return False
|
|
277
|
+
|
|
278
|
+
try:
|
|
279
|
+
success = await self.agent.client.notify_graceful_shutdown(
|
|
280
|
+
self.agent.node_id
|
|
281
|
+
)
|
|
282
|
+
if self.agent.dev_mode and success:
|
|
283
|
+
log_success("Graceful shutdown notification sent")
|
|
284
|
+
return success
|
|
285
|
+
except Exception as e:
|
|
286
|
+
if self.agent.dev_mode:
|
|
287
|
+
log_error(f"Shutdown notification failed: {e}")
|
|
288
|
+
return False
|
|
289
|
+
|
|
290
|
+
def setup_fast_lifecycle_signal_handlers(
|
|
291
|
+
self,
|
|
292
|
+
) -> None: # pragma: no cover - requires OS signal integration
|
|
293
|
+
"""
|
|
294
|
+
Setup signal handler for fast lifecycle status while allowing uvicorn to perform graceful shutdown.
|
|
295
|
+
|
|
296
|
+
- Only intercepts SIGTERM to mark the agent offline and notify AgentField immediately.
|
|
297
|
+
- Leaves SIGINT (Ctrl+C) to uvicorn so its shutdown hooks run and resources are cleaned up.
|
|
298
|
+
"""
|
|
299
|
+
|
|
300
|
+
def signal_handler(signum: int, frame) -> None:
|
|
301
|
+
"""Handle SIGTERM: mark offline, notify AgentField, then re-emit the signal for default handling."""
|
|
302
|
+
signal_name = "SIGTERM" if signum == signal.SIGTERM else "SIGINT"
|
|
303
|
+
|
|
304
|
+
if self.agent.dev_mode:
|
|
305
|
+
log_warn(
|
|
306
|
+
f"{signal_name} received - initiating graceful shutdown via uvicorn"
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
# Set shutdown flag
|
|
310
|
+
self.agent._shutdown_requested = True
|
|
311
|
+
self.agent._current_status = AgentStatus.OFFLINE
|
|
312
|
+
|
|
313
|
+
# Best-effort immediate notification to AgentField
|
|
314
|
+
try:
|
|
315
|
+
success = self.agent.client.notify_graceful_shutdown_sync(
|
|
316
|
+
self.agent.node_id
|
|
317
|
+
)
|
|
318
|
+
if self.agent.dev_mode:
|
|
319
|
+
state = "sent" if success else "failed"
|
|
320
|
+
log_info(f"Shutdown notification {state}")
|
|
321
|
+
except Exception as e:
|
|
322
|
+
if self.agent.dev_mode:
|
|
323
|
+
log_error(f"Shutdown notification error: {e}")
|
|
324
|
+
|
|
325
|
+
# IMPORTANT: Do not perform heavy cleanup here. Let FastAPI/uvicorn shutdown events handle it.
|
|
326
|
+
# Re-install default handler and re-emit the same signal so uvicorn orchestrates cleanup.
|
|
327
|
+
try:
|
|
328
|
+
signal.signal(signum, signal.SIG_DFL)
|
|
329
|
+
os.kill(os.getpid(), signum)
|
|
330
|
+
except Exception:
|
|
331
|
+
# Fallback: polite exit (still allows finally blocks/atexit to run)
|
|
332
|
+
import sys
|
|
333
|
+
|
|
334
|
+
sys.exit(0)
|
|
335
|
+
|
|
336
|
+
try:
|
|
337
|
+
# Only register for SIGTERM; leave SIGINT (Ctrl+C) to uvicorn
|
|
338
|
+
signal.signal(signal.SIGTERM, signal_handler)
|
|
339
|
+
|
|
340
|
+
if self.agent.dev_mode:
|
|
341
|
+
log_debug("Fast lifecycle signal handler registered (SIGTERM only)")
|
|
342
|
+
except Exception as e:
|
|
343
|
+
if self.agent.dev_mode:
|
|
344
|
+
log_error(f"Failed to setup signal handlers: {e}")
|
|
345
|
+
|
|
346
|
+
async def register_with_fast_lifecycle(
|
|
347
|
+
self, port: int
|
|
348
|
+
) -> bool: # pragma: no cover - fast-path relies on external coordination
|
|
349
|
+
"""
|
|
350
|
+
Register agent with immediate status reporting for fast lifecycle.
|
|
351
|
+
|
|
352
|
+
Args:
|
|
353
|
+
port: The port the agent is running on
|
|
354
|
+
|
|
355
|
+
Returns:
|
|
356
|
+
True if registration was successful, False otherwise
|
|
357
|
+
"""
|
|
358
|
+
from agentfield.agent import _build_callback_candidates, _resolve_callback_url
|
|
359
|
+
|
|
360
|
+
if not self.agent.base_url:
|
|
361
|
+
self.agent.callback_candidates = _build_callback_candidates(
|
|
362
|
+
self.agent.callback_url, port
|
|
363
|
+
)
|
|
364
|
+
if self.agent.callback_candidates:
|
|
365
|
+
self.agent.base_url = self.agent.callback_candidates[0]
|
|
366
|
+
log_debug(
|
|
367
|
+
f"Fast lifecycle - Resolved callback URL during registration: {self.agent.base_url}"
|
|
368
|
+
)
|
|
369
|
+
else:
|
|
370
|
+
self.agent.base_url = _resolve_callback_url(
|
|
371
|
+
self.agent.callback_url, port
|
|
372
|
+
)
|
|
373
|
+
log_debug(
|
|
374
|
+
f"Fast lifecycle - Resolved callback URL during registration: {self.agent.base_url}"
|
|
375
|
+
)
|
|
376
|
+
else:
|
|
377
|
+
import urllib.parse
|
|
378
|
+
|
|
379
|
+
parsed = urllib.parse.urlparse(self.agent.base_url)
|
|
380
|
+
if parsed.port != port:
|
|
381
|
+
self.agent.base_url = f"{parsed.scheme}://{parsed.hostname}:{port}"
|
|
382
|
+
log_debug(
|
|
383
|
+
f"Fast lifecycle - Updated port in existing callback URL: {self.agent.base_url}"
|
|
384
|
+
)
|
|
385
|
+
else:
|
|
386
|
+
log_debug(
|
|
387
|
+
f"Fast lifecycle - Using existing callback URL: {self.agent.base_url}"
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
if not self.agent.callback_candidates:
|
|
391
|
+
self.agent.callback_candidates = _build_callback_candidates(
|
|
392
|
+
self.agent.base_url, port
|
|
393
|
+
)
|
|
394
|
+
elif (
|
|
395
|
+
self.agent.base_url
|
|
396
|
+
and self.agent.callback_candidates
|
|
397
|
+
and self.agent.callback_candidates[0] != self.agent.base_url
|
|
398
|
+
):
|
|
399
|
+
if self.agent.base_url in self.agent.callback_candidates:
|
|
400
|
+
self.agent.callback_candidates.remove(self.agent.base_url)
|
|
401
|
+
self.agent.callback_candidates.insert(0, self.agent.base_url)
|
|
402
|
+
|
|
403
|
+
log_debug(f"Fast lifecycle - Final callback URL: {self.agent.base_url}")
|
|
404
|
+
log_debug(
|
|
405
|
+
f"Fast lifecycle - Original callback_url parameter: {self.agent.callback_url}"
|
|
406
|
+
)
|
|
407
|
+
log_debug(
|
|
408
|
+
f"Fast lifecycle - AGENT_CALLBACK_URL env var: {os.environ.get('AGENT_CALLBACK_URL', 'NOT_SET')}"
|
|
409
|
+
)
|
|
410
|
+
log_debug(f"Fast lifecycle - Port: {port}")
|
|
411
|
+
|
|
412
|
+
try:
|
|
413
|
+
if self.agent.dev_mode:
|
|
414
|
+
log_info(
|
|
415
|
+
f"Fast registration with AgentField server at {self.agent.agentfield_server}"
|
|
416
|
+
)
|
|
417
|
+
log_info(f"Using callback URL: {self.agent.base_url}")
|
|
418
|
+
|
|
419
|
+
# Register with STARTING status for immediate visibility
|
|
420
|
+
discovery_payload = self.agent._build_callback_discovery_payload()
|
|
421
|
+
|
|
422
|
+
success, payload = await self.agent.client.register_agent_with_status(
|
|
423
|
+
node_id=self.agent.node_id,
|
|
424
|
+
reasoners=self.agent.reasoners,
|
|
425
|
+
skills=self.agent.skills,
|
|
426
|
+
base_url=self.agent.base_url,
|
|
427
|
+
status=AgentStatus.STARTING,
|
|
428
|
+
discovery=discovery_payload,
|
|
429
|
+
vc_metadata=self.agent._build_vc_metadata(),
|
|
430
|
+
)
|
|
431
|
+
|
|
432
|
+
if success:
|
|
433
|
+
if payload:
|
|
434
|
+
self.agent._apply_discovery_response(payload)
|
|
435
|
+
if self.agent.dev_mode:
|
|
436
|
+
log_success(
|
|
437
|
+
f"Fast registration successful - Status: {AgentStatus.STARTING.value}"
|
|
438
|
+
)
|
|
439
|
+
self.agent.agentfield_connected = True
|
|
440
|
+
|
|
441
|
+
# Attempt DID registration after successful AgentField registration
|
|
442
|
+
if self.agent.did_manager:
|
|
443
|
+
did_success = self.agent._register_agent_with_did()
|
|
444
|
+
if not did_success and self.agent.dev_mode:
|
|
445
|
+
log_warn(
|
|
446
|
+
"DID registration failed, continuing without DID functionality"
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
return True
|
|
450
|
+
else:
|
|
451
|
+
if self.agent.dev_mode:
|
|
452
|
+
log_error("Fast registration failed")
|
|
453
|
+
self.agent.agentfield_connected = False
|
|
454
|
+
return False
|
|
455
|
+
|
|
456
|
+
except Exception as e:
|
|
457
|
+
self.agent.agentfield_connected = False
|
|
458
|
+
if self.agent.dev_mode:
|
|
459
|
+
log_warn(f"Fast registration error: {e}")
|
|
460
|
+
return False
|
|
461
|
+
|
|
462
|
+
async def enhanced_heartbeat_loop(self, interval: int) -> None:
|
|
463
|
+
"""
|
|
464
|
+
Background loop for sending enhanced heartbeats with status and MCP information.
|
|
465
|
+
|
|
466
|
+
Args:
|
|
467
|
+
interval: Heartbeat interval in seconds
|
|
468
|
+
"""
|
|
469
|
+
if self.agent.dev_mode:
|
|
470
|
+
log_debug(f"Enhanced heartbeat loop started (interval: {interval}s)")
|
|
471
|
+
|
|
472
|
+
while not self.agent._shutdown_requested:
|
|
473
|
+
try:
|
|
474
|
+
# Send enhanced heartbeat
|
|
475
|
+
success = await self.send_enhanced_heartbeat()
|
|
476
|
+
|
|
477
|
+
if not success and self.agent.dev_mode:
|
|
478
|
+
log_warn("Enhanced heartbeat failed - retrying next cycle")
|
|
479
|
+
|
|
480
|
+
# Wait for next heartbeat interval
|
|
481
|
+
await asyncio.sleep(interval)
|
|
482
|
+
|
|
483
|
+
except asyncio.CancelledError:
|
|
484
|
+
if self.agent.dev_mode:
|
|
485
|
+
log_debug("Enhanced heartbeat loop cancelled")
|
|
486
|
+
break
|
|
487
|
+
except Exception as e:
|
|
488
|
+
if self.agent.dev_mode:
|
|
489
|
+
log_error(f"Enhanced heartbeat loop error: {e}")
|
|
490
|
+
# Continue loop even on errors
|
|
491
|
+
await asyncio.sleep(interval)
|
|
492
|
+
|
|
493
|
+
if self.agent.dev_mode:
|
|
494
|
+
log_debug("Enhanced heartbeat loop stopped")
|