uagents-core 0.3.6__tar.gz → 0.3.8__tar.gz

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 (23) hide show
  1. {uagents_core-0.3.6 → uagents_core-0.3.8}/PKG-INFO +1 -1
  2. {uagents_core-0.3.6 → uagents_core-0.3.8}/pyproject.toml +1 -1
  3. {uagents_core-0.3.6 → uagents_core-0.3.8}/uagents_core/types.py +29 -0
  4. {uagents_core-0.3.6 → uagents_core-0.3.8}/uagents_core/utils/registration.py +336 -98
  5. {uagents_core-0.3.6 → uagents_core-0.3.8}/uagents_core/utils/resolver.py +64 -3
  6. {uagents_core-0.3.6 → uagents_core-0.3.8}/README.md +0 -0
  7. {uagents_core-0.3.6 → uagents_core-0.3.8}/uagents_core/__init__.py +0 -0
  8. {uagents_core-0.3.6 → uagents_core-0.3.8}/uagents_core/config.py +0 -0
  9. {uagents_core-0.3.6 → uagents_core-0.3.8}/uagents_core/contrib/__init__.py +0 -0
  10. {uagents_core-0.3.6 → uagents_core-0.3.8}/uagents_core/contrib/protocols/__init__.py +0 -0
  11. {uagents_core-0.3.6 → uagents_core-0.3.8}/uagents_core/contrib/protocols/chat/__init__.py +0 -0
  12. {uagents_core-0.3.6 → uagents_core-0.3.8}/uagents_core/contrib/protocols/subscriptions/__init__.py +0 -0
  13. {uagents_core-0.3.6 → uagents_core-0.3.8}/uagents_core/envelope.py +0 -0
  14. {uagents_core-0.3.6 → uagents_core-0.3.8}/uagents_core/helpers.py +0 -0
  15. {uagents_core-0.3.6 → uagents_core-0.3.8}/uagents_core/identity.py +0 -0
  16. {uagents_core-0.3.6 → uagents_core-0.3.8}/uagents_core/logger.py +0 -0
  17. {uagents_core-0.3.6 → uagents_core-0.3.8}/uagents_core/models.py +0 -0
  18. {uagents_core-0.3.6 → uagents_core-0.3.8}/uagents_core/protocol.py +0 -0
  19. {uagents_core-0.3.6 → uagents_core-0.3.8}/uagents_core/registration.py +0 -0
  20. {uagents_core-0.3.6 → uagents_core-0.3.8}/uagents_core/storage.py +0 -0
  21. {uagents_core-0.3.6 → uagents_core-0.3.8}/uagents_core/utils/__init__.py +0 -0
  22. {uagents_core-0.3.6 → uagents_core-0.3.8}/uagents_core/utils/messages.py +0 -0
  23. {uagents_core-0.3.6 → uagents_core-0.3.8}/uagents_core/utils/subscriptions.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: uagents-core
3
- Version: 0.3.6
3
+ Version: 0.3.8
4
4
  Summary: Core components for agent based systems
5
5
  License: Apache 2.0
6
6
  Author: Ed FitzGerald
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "uagents-core"
3
- version = "0.3.6"
3
+ version = "0.3.8"
4
4
  description = "Core components for agent based systems"
5
5
  authors = [
6
6
  { name = "Ed FitzGerald", email = "edward.fitzgerald@fetch.ai" },
@@ -1,5 +1,6 @@
1
1
  import uuid
2
2
  from abc import ABC, abstractmethod
3
+ from datetime import datetime
3
4
  from enum import Enum
4
5
  from typing import Any, Literal
5
6
 
@@ -22,6 +23,34 @@ class AgentInfo(BaseModel):
22
23
  endpoints: list[AgentEndpoint]
23
24
  protocols: list[str]
24
25
  metadata: dict[str, Any] | None = None
26
+ agent_type: AgentType
27
+ port: int | None = None
28
+
29
+
30
+ class AgentRecord(BaseModel):
31
+ address: str
32
+ weight: float
33
+
34
+
35
+ class DomainRecord(BaseModel):
36
+ name: str
37
+ agents: list[AgentRecord]
38
+
39
+
40
+ class DomainStatus(Enum):
41
+ Registered = "Registered"
42
+ Pending = "Pending"
43
+ Checking = "Checking"
44
+ Updating = "Updating"
45
+ Deleting = "Deleting"
46
+ Failed = "Failed"
47
+
48
+
49
+ class Domain(BaseModel):
50
+ name: str
51
+ status: DomainStatus
52
+ expiry: datetime | None = None
53
+ assigned_agents: list[AgentRecord]
25
54
 
26
55
 
27
56
  class DeliveryStatus(str, Enum):
@@ -2,10 +2,12 @@
2
2
  This module provides methods to register your identity with the Fetch.ai services.
3
3
  """
4
4
 
5
+ from typing import Literal
5
6
  import urllib.parse
6
7
 
7
8
  import requests
8
- from pydantic import BaseModel
9
+ from pydantic import BaseModel, Field, model_validator
10
+ from json import JSONDecodeError
9
11
 
10
12
  from uagents_core.config import (
11
13
  DEFAULT_ALMANAC_API_PATH,
@@ -14,9 +16,10 @@ from uagents_core.config import (
14
16
  DEFAULT_REQUEST_TIMEOUT,
15
17
  AgentverseConfig,
16
18
  )
19
+ from uagents_core.contrib.protocols.chat import chat_protocol_spec
17
20
  from uagents_core.identity import Identity
18
21
  from uagents_core.logger import get_logger
19
- from uagents_core.protocol import is_valid_protocol_digest
22
+ from uagents_core.protocol import ProtocolSpecification, is_valid_protocol_digest
20
23
  from uagents_core.registration import (
21
24
  AgentRegistrationAttestation,
22
25
  AgentRegistrationAttestationBatch,
@@ -26,9 +29,8 @@ from uagents_core.registration import (
26
29
  ChallengeRequest,
27
30
  ChallengeResponse,
28
31
  RegistrationRequest,
29
- RegistrationResponse,
30
32
  )
31
- from uagents_core.types import AddressPrefix, AgentEndpoint
33
+ from uagents_core.types import AddressPrefix, AgentEndpoint, AgentType
32
34
 
33
35
  logger = get_logger("uagents_core.utils.registration")
34
36
 
@@ -55,35 +57,125 @@ class AgentRegistrationInput:
55
57
  self.metadata = metadata
56
58
 
57
59
 
58
- def _send_post_request(
60
+ class AgentverseRegistrationRequest(BaseModel):
61
+ """
62
+ A model containing all information for a user to register
63
+ their pre-existing agent to Agentverse.
64
+ """
65
+
66
+ name: str = Field(description="Agent name in Agentverse")
67
+ endpoint: str = Field(
68
+ description="Endpoint where the existing agent is accessible at."
69
+ )
70
+ protocols: list[str] = Field(
71
+ description="List of protocols supported by the agent."
72
+ )
73
+ type: AgentType = Field(
74
+ default="custom", description="Agentverse registration type"
75
+ )
76
+ description: str | None = Field(
77
+ default=None,
78
+ description="Agent short description, shown on its Agentverse profile.",
79
+ )
80
+ readme: str | None = Field(default=None, description="Agent skills description.")
81
+ avatar_url: str | None = Field(
82
+ default=None,
83
+ description="Agent avatar url to be shown on its Agentverse profile.",
84
+ )
85
+ active: bool = Field(default=True, description="Set agent as active immediatly")
86
+
87
+ @model_validator(mode="after")
88
+ def check_request(self) -> "AgentverseRegistrationRequest":
89
+ # check endpoint
90
+ result = urllib.parse.urlparse(self.endpoint)
91
+ if not all([result.scheme, result.netloc]):
92
+ raise ValueError(f"Invalid endpoint provided: {self.endpoint}")
93
+
94
+ # check protocol digests
95
+ for proto_digest in self.protocols:
96
+ if not is_valid_protocol_digest(proto_digest):
97
+ raise ValueError(
98
+ f"Invalid protocol digest provided: {proto_digest}",
99
+ )
100
+ return self
101
+
102
+
103
+ class RegistrationRequestCredentials(BaseModel):
104
+ agentverse_api_key: str = Field(
105
+ description="Agentverse API key generated by the owner of the agent"
106
+ )
107
+ agent_seed_phrase: str = Field(
108
+ description="The secret seed phrase used to create the agent identity"
109
+ )
110
+
111
+
112
+ class AgentverseRequestException(Exception):
113
+ def __init__(self, *args, from_exc: Exception):
114
+ self.from_exc = from_exc
115
+ super().__init__(*args)
116
+
117
+
118
+ def _send_http_request_agentverse(
119
+ request_type: Literal["post", "put"],
59
120
  url: str,
60
121
  data: BaseModel,
61
122
  *,
62
123
  headers: dict[str, str] | None = None,
63
124
  timeout: int = DEFAULT_REQUEST_TIMEOUT,
64
- ) -> tuple[bool, requests.Response | None]:
125
+ ) -> requests.Response:
65
126
  final_headers: dict[str, str] = {"content-type": "application/json"}
66
127
  if headers:
67
128
  final_headers.update(headers)
129
+
130
+ send_request = requests.post if request_type == "post" else requests.put
131
+
68
132
  try:
69
- response: requests.Response = requests.post(
133
+ response: requests.Response = send_request(
70
134
  url=url,
71
135
  headers=final_headers,
72
136
  data=data.model_dump_json(),
73
137
  timeout=timeout,
74
138
  )
75
139
  response.raise_for_status()
76
- return True, response
77
- except requests.RequestException as e:
78
- error_detail = getattr(e, "response", None)
79
- if error_detail is not None:
80
- error_detail = error_detail.text
81
- logger.error(
82
- msg=f"Error submitting request: {error_detail}",
83
- extra={"url": url, "data": data.model_dump_json()},
84
- exc_info=e,
85
- )
86
- return False, None
140
+ except Exception as e:
141
+ err_msg = ""
142
+
143
+ if isinstance(e, requests.ConnectionError):
144
+ err_msg += f"Connection error {e.strerror}."
145
+ elif isinstance(e, requests.Timeout):
146
+ err_msg += "Operation timed out."
147
+ elif isinstance(e, requests.HTTPError):
148
+ code = e.response.status_code
149
+ try:
150
+ content = e.response.json()["detail"]
151
+ except (JSONDecodeError, KeyError):
152
+ content = e.response.content.decode()
153
+ if code in [401, 406, 409]:
154
+ err_msg += content
155
+ elif code == 500:
156
+ err_msg += "Unexpected server error."
157
+ else:
158
+ err_msg += f"HTTP error: {code} {content}"
159
+ elif isinstance(e, requests.RequestException):
160
+ err_msg += f"Unexpected request error: {e}."
161
+ else:
162
+ err_msg += f"Unexpected error: {e}."
163
+
164
+ raise AgentverseRequestException(err_msg, from_exc=e)
165
+
166
+ return response
167
+
168
+
169
+ def _send_post_request_agentverse(
170
+ url: str,
171
+ data: BaseModel,
172
+ *,
173
+ headers: dict[str, str] | None = None,
174
+ timeout: int = DEFAULT_REQUEST_TIMEOUT,
175
+ ) -> requests.Response:
176
+ return _send_http_request_agentverse(
177
+ "post", url, data, headers=headers, timeout=timeout
178
+ )
87
179
 
88
180
 
89
181
  def _build_signed_attestation(
@@ -94,9 +186,11 @@ def _build_signed_attestation(
94
186
  ]
95
187
 
96
188
  attestation = AgentRegistrationAttestation(
97
- agent_identifier=f"{item.prefix}://{item.identity.address}"
98
- if item.prefix
99
- else item.identity.address,
189
+ agent_identifier=(
190
+ f"{item.prefix}://{item.identity.address}"
191
+ if item.prefix
192
+ else item.identity.address
193
+ ),
100
194
  protocols=item.protocol_digests,
101
195
  endpoints=agent_endpoints,
102
196
  metadata=item.metadata,
@@ -106,6 +200,49 @@ def _build_signed_attestation(
106
200
  return attestation
107
201
 
108
202
 
203
+ def _register_in_almanac(
204
+ identity: Identity,
205
+ endpoints: list[str],
206
+ protocol_digests: list[str],
207
+ metadata: dict[str, str | list[str] | dict[str, str]] | None = None,
208
+ prefix: AddressPrefix | None = None,
209
+ *,
210
+ agentverse_config: AgentverseConfig | None = None,
211
+ timeout: int = DEFAULT_REQUEST_TIMEOUT,
212
+ ) -> requests.Response:
213
+ """
214
+ Register the identity with the Almanac API to make it discoverable by other agents.
215
+
216
+ Args:
217
+ identity (Identity): The identity of the agent.
218
+ prefix (AddressPrefix | None): The prefix for the agent identifier.
219
+ endpoints (list[str]): The endpoints that the agent can be reached at.
220
+ protocol_digests (list[str]): The digests of the protocol that the agent supports
221
+ agentverse_config (AgentverseConfig): The configuration for the agentverse API
222
+ timeout (int): The timeout for the request
223
+ """
224
+ # get the almanac API endpoint
225
+ agentverse_config = agentverse_config or AgentverseConfig()
226
+ almanac_api = urllib.parse.urljoin(agentverse_config.url, DEFAULT_ALMANAC_API_PATH)
227
+
228
+ # create the attestation
229
+ item = AgentRegistrationInput(
230
+ identity=identity,
231
+ prefix=prefix,
232
+ endpoints=endpoints,
233
+ protocol_digests=protocol_digests,
234
+ metadata=metadata,
235
+ )
236
+ attestation = _build_signed_attestation(item)
237
+
238
+ logger.info(msg="Registering with Almanac API", extra=attestation.model_dump())
239
+
240
+ # submit the attestation to the API
241
+ return _send_post_request_agentverse(
242
+ url=f"{almanac_api}/agents", data=attestation, timeout=timeout
243
+ )
244
+
245
+
109
246
  def register_in_almanac(
110
247
  identity: Identity,
111
248
  endpoints: list[str],
@@ -149,27 +286,21 @@ def register_in_almanac(
149
286
  )
150
287
  return False
151
288
 
152
- # get the almanac API endpoint
153
- agentverse_config = agentverse_config or AgentverseConfig()
154
- almanac_api = urllib.parse.urljoin(agentverse_config.url, DEFAULT_ALMANAC_API_PATH)
155
-
156
- # create the attestation
157
- item = AgentRegistrationInput(
158
- identity=identity,
159
- prefix=prefix,
160
- endpoints=endpoints,
161
- protocol_digests=protocol_digests,
162
- metadata=metadata,
163
- )
164
- attestation = _build_signed_attestation(item)
165
-
166
- logger.info(msg="Registering with Almanac API", extra=attestation.model_dump())
289
+ try:
290
+ _register_in_almanac(
291
+ identity,
292
+ endpoints,
293
+ protocol_digests,
294
+ metadata,
295
+ prefix,
296
+ agentverse_config=agentverse_config,
297
+ timeout=timeout,
298
+ )
299
+ return True
300
+ except AgentverseRequestException as e:
301
+ logger.error(msg=str(e), exc_info=e.from_exc)
167
302
 
168
- # submit the attestation to the API
169
- status, _ = _send_post_request(
170
- url=f"{almanac_api}/agents", data=attestation, timeout=timeout
171
- )
172
- return status
303
+ return False
173
304
 
174
305
 
175
306
  def register_batch_in_almanac(
@@ -252,23 +383,27 @@ def register_batch_in_almanac(
252
383
  )
253
384
 
254
385
  # submit the attestation to the API
255
- status, _ = _send_post_request(
256
- url=f"{almanac_api}/agents/batch",
257
- data=attestation_batch,
258
- timeout=timeout,
259
- )
260
- return status, invalid_identities
386
+ try:
387
+ _send_post_request_agentverse(
388
+ url=f"{almanac_api}/agents/batch",
389
+ data=attestation_batch,
390
+ timeout=timeout,
391
+ )
392
+ return True, invalid_identities
393
+ except AgentverseRequestException as e:
394
+ logger.error(msg=str(e), exc_info=e.from_exc)
261
395
 
396
+ return False, invalid_identities
262
397
 
263
- # associate user account with your agent
264
- def register_in_agentverse(
398
+
399
+ def _register_in_agentverse(
265
400
  request: AgentverseConnectRequest,
266
401
  identity: Identity,
267
402
  *,
268
403
  agent_details: AgentUpdates | None = None,
269
404
  agentverse_config: AgentverseConfig | None = None,
270
405
  timeout: int = DEFAULT_REQUEST_TIMEOUT,
271
- ) -> bool:
406
+ ):
272
407
  """
273
408
  Register an agent in Agentverse and update its details if provided.
274
409
 
@@ -317,18 +452,18 @@ def register_in_agentverse(
317
452
  logger.debug(
318
453
  msg="Requesting mailbox access challenge", extra=registration_metadata
319
454
  )
320
- status, response = _send_post_request(
321
- url=challenge_api,
322
- data=challenge_request,
323
- headers={"authorization": f"Bearer {request.user_token}"},
324
- timeout=timeout,
325
- )
326
- if not status or not response:
327
- logger.error(
328
- msg="Error requesting mailbox access challenge",
329
- extra=registration_metadata,
455
+ try:
456
+ response = _send_post_request_agentverse(
457
+ url=challenge_api,
458
+ data=challenge_request,
459
+ headers={"authorization": f"Bearer {request.user_token}"},
460
+ timeout=timeout,
461
+ )
462
+ except AgentverseRequestException as e:
463
+ raise AgentverseRequestException(
464
+ f"failed to request proof-of-ownership challenge. {str(e)}",
465
+ from_exc=e.from_exc,
330
466
  )
331
- return False
332
467
 
333
468
  challenge = ChallengeResponse.model_validate_json(response.text)
334
469
  registration_payload = RegistrationRequest(
@@ -338,34 +473,20 @@ def register_in_agentverse(
338
473
  endpoint=request.endpoint,
339
474
  agent_type=request.agent_type,
340
475
  )
341
- status, response = _send_post_request(
476
+
477
+ response = _send_post_request_agentverse(
342
478
  url=registration_api,
343
479
  data=registration_payload,
344
480
  headers={"authorization": f"Bearer {request.user_token}"},
345
481
  timeout=timeout,
346
482
  )
347
- if not status or not response:
348
- logger.error(
349
- msg="Error registering agent with Agentverse",
350
- extra=registration_metadata,
351
- )
352
- return False
353
- else:
354
- registration_response = RegistrationResponse.model_validate_json(
355
- response.text
356
- )
357
- if registration_response.success:
358
- logger.info(
359
- msg=f"Successfully registered as {request.agent_type} agent in Agentverse",
360
- extra=registration_metadata,
361
- )
362
483
 
363
484
  if not agent_details:
364
485
  logger.debug(
365
486
  msg="No agent details provided; skipping agent update",
366
487
  extra=registration_metadata,
367
488
  )
368
- return True
489
+ return
369
490
 
370
491
  # update the readme and the name of the agent to make it easier to find
371
492
  logger.debug(
@@ -373,31 +494,55 @@ def register_in_agentverse(
373
494
  extra=registration_metadata,
374
495
  )
375
496
  try:
376
- response = requests.put(
497
+ response = _send_http_request_agentverse(
498
+ request_type="put",
377
499
  url=f"{registration_api}/{agent_address}",
378
500
  headers={
379
501
  "content-type": "application/json",
380
502
  "authorization": f"Bearer {request.user_token}",
381
503
  },
382
- data=agent_details.model_dump_json(),
504
+ data=agent_details,
383
505
  timeout=timeout,
384
506
  )
385
- response.raise_for_status()
386
- logger.info(
387
- msg="Completed registering agent with Agentverse",
388
- extra=registration_metadata,
507
+ except AgentverseRequestException as e:
508
+ logger.warning(f"failed to upload agent details. {str(e)}")
509
+
510
+
511
+ # associate user account with your agent
512
+ def register_in_agentverse(
513
+ request: AgentverseConnectRequest,
514
+ identity: Identity,
515
+ *,
516
+ agent_details: AgentUpdates | None = None,
517
+ agentverse_config: AgentverseConfig | None = None,
518
+ timeout: int = DEFAULT_REQUEST_TIMEOUT,
519
+ ) -> bool:
520
+ """
521
+ Register an agent in Agentverse and update its details if provided.
522
+
523
+ Args:
524
+ request (AgentverseConnectRequest): The request containing the agent details.
525
+ identity (Identity): The identity of the agent.
526
+ agent_details (AgentUpdates | None): The agent details to update.
527
+ agentverse_config (AgentverseConfig | None): The configuration for the agentverse API
528
+ timeout (int): The timeout for the requests
529
+ """
530
+ try:
531
+ _register_in_agentverse(
532
+ request,
533
+ identity,
534
+ agent_details=agent_details,
535
+ agentverse_config=agentverse_config,
536
+ timeout=timeout,
389
537
  )
390
538
  return True
391
- except requests.RequestException as e:
392
- logger.error(
393
- msg="Error registering agent with Agentverse",
394
- extra=registration_metadata,
395
- exc_info=e,
396
- )
397
- return False
539
+ except AgentverseRequestException as e:
540
+ logger.error(msg=str(e), exc_info=e.from_exc)
541
+
542
+ return False
398
543
 
399
544
 
400
- def update_agent_status(active: bool, identity: Identity):
545
+ def _update_agent_status(active: bool, identity: Identity):
401
546
  """
402
547
  Update the agent's active/inactive status in the Almanac API.
403
548
 
@@ -417,15 +562,108 @@ def update_agent_status(active: bool, identity: Identity):
417
562
  extra=status_update.model_dump(),
418
563
  )
419
564
 
420
- status, _ = _send_post_request(
565
+ _send_post_request_agentverse(
421
566
  url=f"{almanac_api}/agents/{identity.address}/status",
422
567
  data=status_update,
423
568
  )
424
569
 
425
- if status:
426
- logger.info(
427
- msg=f"Agent status updated to {'active' if active else 'inactive'}",
428
- extra={"agent_address": identity.address},
570
+
571
+ def update_agent_status(active: bool, identity: Identity) -> bool:
572
+ """
573
+ Update the agent's active/inactive status in the Almanac API.
574
+
575
+ Args:
576
+ active (bool): The status of the agent.
577
+ identity (Identity): The identity of the agent.
578
+ """
579
+ try:
580
+ _update_agent_status(active, identity)
581
+ return True
582
+ except AgentverseRequestException as e:
583
+ logger.error(msg=str(e), exc_info=e.from_exc)
584
+
585
+ return False
586
+
587
+
588
+ def register_agent(
589
+ agent_registration: AgentverseRegistrationRequest,
590
+ agentverse_config: AgentverseConfig,
591
+ credentials: RegistrationRequestCredentials,
592
+ ):
593
+ identity = Identity.from_seed(credentials.agent_seed_phrase, 0)
594
+ endpoints = [agent_registration.endpoint]
595
+ protos = agent_registration.protocols
596
+
597
+ connect_request = AgentverseConnectRequest(
598
+ user_token=credentials.agentverse_api_key,
599
+ agent_type=agent_registration.type,
600
+ endpoint=endpoints[0],
601
+ )
602
+
603
+ agent_details = AgentUpdates(
604
+ name=agent_registration.name,
605
+ readme=agent_registration.readme,
606
+ avatar_url=agent_registration.avatar_url,
607
+ short_description=agent_registration.description,
608
+ agent_type=agent_registration.type,
609
+ )
610
+
611
+ # register the agent to almanac
612
+ try:
613
+ logger.info("registering to Almanac...")
614
+ _register_in_almanac(
615
+ identity, endpoints, protos, agentverse_config=agentverse_config
429
616
  )
617
+ logger.info("successfully registered to Almanac.")
618
+ except AgentverseRequestException as e:
619
+ logger.error(f"failed to register to Almanac. {str(e)}")
620
+ return
621
+
622
+ # register the agent to agentverse
623
+ try:
624
+ logger.info("registering to Agentverse...")
625
+ _register_in_agentverse(
626
+ connect_request,
627
+ identity,
628
+ agent_details=agent_details,
629
+ agentverse_config=agentverse_config,
630
+ )
631
+ logger.info("successfully registered to Agentverse.")
632
+ except AgentverseRequestException as e:
633
+ logger.error(f"failed to register to Agentverse. {str(e)}")
634
+ return
635
+
636
+ # set agent as active
637
+ if agent_registration.active:
638
+ try:
639
+ logger.info("setting agent as active...")
640
+ _update_agent_status(True, identity)
641
+ logger.info("successfully set agent to active.")
642
+ except AgentverseRequestException as e:
643
+ logger.warning(f"failed to set agent as active. {str(e)}")
644
+
645
+
646
+ def register_chat_agent(
647
+ name: str,
648
+ endpoint: str,
649
+ active: bool,
650
+ credentials: RegistrationRequestCredentials,
651
+ description: str | None = None,
652
+ readme: str | None = None,
653
+ avatar_url: str | None = None,
654
+ ):
655
+ chat_protocol = [
656
+ ProtocolSpecification.compute_digest(chat_protocol_spec.manifest())
657
+ ]
658
+ request = AgentverseRegistrationRequest(
659
+ name=name,
660
+ endpoint=endpoint,
661
+ protocols=chat_protocol,
662
+ active=active,
663
+ description=description,
664
+ readme=readme,
665
+ avatar_url=avatar_url,
666
+ )
667
+ config = AgentverseConfig()
430
668
 
431
- return status
669
+ register_agent(request, config, credentials)
@@ -14,11 +14,55 @@ from uagents_core.config import (
14
14
  from uagents_core.helpers import weighted_random_sample
15
15
  from uagents_core.identity import parse_identifier
16
16
  from uagents_core.logger import get_logger
17
- from uagents_core.types import Resolver
17
+ from uagents_core.types import Domain, Resolver
18
18
 
19
19
  logger = get_logger("uagents_core.utils.resolver")
20
20
 
21
21
 
22
+ def lookup_address_for_domain(
23
+ agent_identifier: str,
24
+ *,
25
+ agentverse_config: AgentverseConfig | None = None,
26
+ ) -> str | None:
27
+ agentverse_config = agentverse_config or AgentverseConfig()
28
+ almanac_api = urllib.parse.urljoin(agentverse_config.url, DEFAULT_ALMANAC_API_PATH)
29
+
30
+ prefix, domain, _ = parse_identifier(agent_identifier)
31
+ if not domain:
32
+ logger.error(
33
+ "No domain provided in agent identifier",
34
+ extra={"identifier": agent_identifier},
35
+ )
36
+ return None
37
+
38
+ params = {"prefix": prefix} if prefix else None
39
+ try:
40
+ response = requests.get(
41
+ url=f"{almanac_api}/domains/{domain}",
42
+ timeout=DEFAULT_REQUEST_TIMEOUT,
43
+ params=params,
44
+ )
45
+ response.raise_for_status()
46
+ except requests.RequestException as e:
47
+ logger.error(
48
+ msg="Error looking up domain",
49
+ extra={"domain": domain, "exception": str(e)},
50
+ )
51
+ return None
52
+
53
+ domain_record = Domain.model_validate(response.json())
54
+
55
+ agent_records = domain_record.assigned_agents
56
+ if len(agent_records) == 0:
57
+ return None
58
+ elif len(agent_records) == 1:
59
+ return agent_records[0].address
60
+ else:
61
+ addresses = [val.address for val in agent_records]
62
+ weights = [val.weight for val in agent_records]
63
+ return weighted_random_sample(addresses, weights=weights, k=1)[0]
64
+
65
+
22
66
  def lookup_endpoint_for_agent(
23
67
  agent_identifier: str,
24
68
  *,
@@ -34,19 +78,36 @@ def lookup_endpoint_for_agent(
34
78
  Returns:
35
79
  List[str]: The endpoint(s) for the agent.
36
80
  """
37
- _, _, agent_address = parse_identifier(agent_identifier)
38
81
 
39
82
  agentverse_config = agentverse_config or AgentverseConfig()
40
83
  almanac_api = urllib.parse.urljoin(agentverse_config.url, DEFAULT_ALMANAC_API_PATH)
41
84
 
85
+ prefix, domain, agent_address = parse_identifier(agent_identifier)
86
+
87
+ if not agent_address:
88
+ if domain:
89
+ agent_address = lookup_address_for_domain(
90
+ agent_identifier=agent_identifier,
91
+ agentverse_config=agentverse_config,
92
+ )
93
+ else:
94
+ logger.error(
95
+ "No address or domain provided in identifier",
96
+ extra={"identifier": agent_identifier},
97
+ )
98
+ return []
99
+
42
100
  request_meta: dict[str, Any] = {
43
101
  "agent_address": agent_address,
44
102
  "lookup_url": almanac_api,
45
103
  }
46
104
  logger.debug(msg="looking up endpoint for agent", extra=request_meta)
47
105
  try:
106
+ params = {"prefix": prefix} if prefix else None
48
107
  response = requests.get(
49
- url=f"{almanac_api}/agents/{agent_address}", timeout=DEFAULT_REQUEST_TIMEOUT
108
+ url=f"{almanac_api}/agents/{agent_address}",
109
+ params=params,
110
+ timeout=DEFAULT_REQUEST_TIMEOUT,
50
111
  )
51
112
  response.raise_for_status()
52
113
  except requests.RequestException as e:
File without changes