agentscope-runtime 1.0.2__py3-none-any.whl → 1.0.4__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 (58) hide show
  1. agentscope_runtime/adapters/agentscope/stream.py +2 -9
  2. agentscope_runtime/adapters/ms_agent_framework/__init__.py +0 -0
  3. agentscope_runtime/adapters/ms_agent_framework/message.py +205 -0
  4. agentscope_runtime/adapters/ms_agent_framework/stream.py +418 -0
  5. agentscope_runtime/adapters/utils.py +6 -0
  6. agentscope_runtime/cli/commands/deploy.py +383 -0
  7. agentscope_runtime/common/collections/redis_mapping.py +4 -1
  8. agentscope_runtime/common/container_clients/knative_client.py +466 -0
  9. agentscope_runtime/engine/__init__.py +4 -0
  10. agentscope_runtime/engine/app/agent_app.py +48 -5
  11. agentscope_runtime/engine/constant.py +1 -0
  12. agentscope_runtime/engine/deployers/__init__.py +12 -0
  13. agentscope_runtime/engine/deployers/adapter/a2a/__init__.py +31 -1
  14. agentscope_runtime/engine/deployers/adapter/a2a/a2a_protocol_adapter.py +458 -41
  15. agentscope_runtime/engine/deployers/adapter/a2a/a2a_registry.py +76 -0
  16. agentscope_runtime/engine/deployers/adapter/a2a/nacos_a2a_registry.py +749 -0
  17. agentscope_runtime/engine/deployers/agentrun_deployer.py +2 -2
  18. agentscope_runtime/engine/deployers/fc_deployer.py +1506 -0
  19. agentscope_runtime/engine/deployers/knative_deployer.py +290 -0
  20. agentscope_runtime/engine/deployers/kubernetes_deployer.py +3 -0
  21. agentscope_runtime/engine/deployers/utils/docker_image_utils/dockerfile_generator.py +8 -2
  22. agentscope_runtime/engine/deployers/utils/docker_image_utils/image_factory.py +5 -0
  23. agentscope_runtime/engine/deployers/utils/net_utils.py +65 -0
  24. agentscope_runtime/engine/runner.py +17 -3
  25. agentscope_runtime/engine/schemas/exception.py +24 -0
  26. agentscope_runtime/engine/services/agent_state/redis_state_service.py +61 -8
  27. agentscope_runtime/engine/services/agent_state/state_service_factory.py +2 -5
  28. agentscope_runtime/engine/services/memory/redis_memory_service.py +129 -25
  29. agentscope_runtime/engine/services/session_history/redis_session_history_service.py +160 -34
  30. agentscope_runtime/engine/tracing/wrapper.py +18 -4
  31. agentscope_runtime/sandbox/__init__.py +14 -6
  32. agentscope_runtime/sandbox/box/base/__init__.py +2 -2
  33. agentscope_runtime/sandbox/box/base/base_sandbox.py +51 -1
  34. agentscope_runtime/sandbox/box/browser/__init__.py +2 -2
  35. agentscope_runtime/sandbox/box/browser/browser_sandbox.py +198 -2
  36. agentscope_runtime/sandbox/box/filesystem/__init__.py +2 -2
  37. agentscope_runtime/sandbox/box/filesystem/filesystem_sandbox.py +99 -2
  38. agentscope_runtime/sandbox/box/gui/__init__.py +2 -2
  39. agentscope_runtime/sandbox/box/gui/gui_sandbox.py +117 -1
  40. agentscope_runtime/sandbox/box/mobile/__init__.py +2 -2
  41. agentscope_runtime/sandbox/box/mobile/mobile_sandbox.py +247 -100
  42. agentscope_runtime/sandbox/box/sandbox.py +98 -65
  43. agentscope_runtime/sandbox/box/shared/routers/generic.py +36 -29
  44. agentscope_runtime/sandbox/build.py +50 -57
  45. agentscope_runtime/sandbox/client/__init__.py +6 -1
  46. agentscope_runtime/sandbox/client/async_http_client.py +339 -0
  47. agentscope_runtime/sandbox/client/base.py +74 -0
  48. agentscope_runtime/sandbox/client/http_client.py +108 -329
  49. agentscope_runtime/sandbox/enums.py +7 -0
  50. agentscope_runtime/sandbox/manager/sandbox_manager.py +264 -4
  51. agentscope_runtime/sandbox/manager/server/app.py +7 -1
  52. agentscope_runtime/version.py +1 -1
  53. {agentscope_runtime-1.0.2.dist-info → agentscope_runtime-1.0.4.dist-info}/METADATA +109 -29
  54. {agentscope_runtime-1.0.2.dist-info → agentscope_runtime-1.0.4.dist-info}/RECORD +58 -46
  55. {agentscope_runtime-1.0.2.dist-info → agentscope_runtime-1.0.4.dist-info}/WHEEL +0 -0
  56. {agentscope_runtime-1.0.2.dist-info → agentscope_runtime-1.0.4.dist-info}/entry_points.txt +0 -0
  57. {agentscope_runtime-1.0.2.dist-info → agentscope_runtime-1.0.4.dist-info}/licenses/LICENSE +0 -0
  58. {agentscope_runtime-1.0.2.dist-info → agentscope_runtime-1.0.4.dist-info}/top_level.txt +0 -0
@@ -1,79 +1,496 @@
1
1
  # -*- coding: utf-8 -*-
2
- import posixpath
3
- from typing import Callable
2
+ """
3
+ A2A Protocol Adapter for FastAPI
4
+
5
+ This module provides the default A2A (Agent-to-Agent) protocol adapter
6
+ implementation for FastAPI applications. It handles agent card configuration,
7
+ wellknown endpoint setup, and task management.
8
+ """
9
+ import os
10
+ import logging
11
+ from typing import Any, Callable, Dict, List, Optional, Union
12
+ from urllib.parse import urljoin
4
13
 
5
14
  from a2a.server.apps import A2AFastAPIApplication
6
15
  from a2a.server.request_handlers import DefaultRequestHandler
7
16
  from a2a.server.tasks import InMemoryTaskStore
8
- from a2a.types import AgentCard, AgentCapabilities, AgentSkill
17
+ from a2a.types import (
18
+ AgentCapabilities,
19
+ AgentCard,
20
+ AgentSkill,
21
+ )
22
+ from a2a.utils import AGENT_CARD_WELL_KNOWN_PATH
23
+ from fastapi import FastAPI
24
+ from pydantic import ConfigDict, BaseModel, field_validator
25
+
26
+ from agentscope_runtime.engine.deployers.utils.net_utils import (
27
+ get_first_non_loopback_ip,
28
+ )
9
29
 
10
30
  from .a2a_agent_adapter import A2AExecutor
31
+ from .a2a_registry import (
32
+ A2ARegistry,
33
+ A2ATransportsProperties,
34
+ )
35
+
36
+ # NOTE: Do NOT import NacosRegistry at module import time to avoid
37
+ # forcing an optional dependency on environments that don't have nacos
38
+ # SDK installed. Registry is optional: users must explicitly provide a
39
+ # registry instance if needed.
40
+ # from .nacos_a2a_registry import NacosRegistry
11
41
  from ..protocol_adapter import ProtocolAdapter
12
42
 
43
+ logger = logging.getLogger(__name__)
44
+
13
45
  A2A_JSON_RPC_URL = "/a2a"
46
+ DEFAULT_WELLKNOWN_PATH = AGENT_CARD_WELL_KNOWN_PATH
47
+ DEFAULT_TASK_TIMEOUT = 60
48
+ DEFAULT_TASK_EVENT_TIMEOUT = 10
49
+ DEFAULT_TRANSPORT = "JSONRPC"
50
+ DEFAULT_INPUT_OUTPUT_MODES = ["text"]
51
+ PORT = int(os.getenv("PORT", "8080"))
52
+ AGENT_VERSION = "1.0.0"
53
+
54
+
55
+ def extract_a2a_config(
56
+ a2a_config: Optional["AgentCardWithRuntimeConfig"] = None,
57
+ ) -> "AgentCardWithRuntimeConfig":
58
+ """Normalize a2a_config to AgentCardWithRuntimeConfig object.
59
+
60
+ Registry resolution priority:
61
+ 1. Use registry from a2a_config if provided
62
+ 2. Fallback to environment variables if a2a_config.registry is
63
+ None
64
+ 3. If neither is available, registry remains None (user doesn't
65
+ want registry)
66
+
67
+ Args:
68
+ a2a_config: Optional AgentCardWithRuntimeConfig instance.
69
+
70
+ Returns:
71
+ Normalized AgentCardWithRuntimeConfig object.
72
+ """
73
+ if a2a_config is None:
74
+ a2a_config = AgentCardWithRuntimeConfig()
75
+
76
+ # Try environment variables only if registry is not explicitly provided
77
+ if a2a_config.registry is None:
78
+ try:
79
+ from .nacos_a2a_registry import create_nacos_registry_from_env
80
+
81
+ env_registry = create_nacos_registry_from_env()
82
+ if env_registry is not None:
83
+ a2a_config.registry = env_registry
84
+ logger.debug("[A2A] Using registry from environment variables")
85
+ except ImportError:
86
+ # Nacos SDK not available, registry remains None
87
+ logger.debug("[A2A] Nacos registry not available")
88
+
89
+ return a2a_config
90
+
91
+
92
+ class AgentCardWithRuntimeConfig(BaseModel):
93
+ """Runtime configuration wrapper for AgentCard.
94
+
95
+ Combines AgentCard (protocol fields) with runtime-specific settings
96
+ (host, port, registry, timeouts, etc.) in a single configuration object.
97
+
98
+ Attributes:
99
+ agent_card: AgentCard object or dict containing protocol fields
100
+ (name, description, url, version, skills, etc.)
101
+ host: Host address for A2A endpoints (default: auto-detected)
102
+ port: Port for A2A endpoints (default: from PORT env var or 8080)
103
+ registry: List of A2A registry instances for service discovery
104
+ task_timeout: Task completion timeout in seconds (default: 60)
105
+ task_event_timeout: Task event timeout in seconds (default: 10)
106
+ wellknown_path: Wellknown endpoint path
107
+ (default: /.wellknown/agent-card.json)
108
+ """
109
+
110
+ agent_card: Optional[Union[AgentCard, Dict[str, Any]]] = None
111
+ host: Optional[str] = None
112
+ port: int = PORT
113
+ registry: Optional[Union[A2ARegistry, List[A2ARegistry]]] = None
114
+ task_timeout: Optional[int] = DEFAULT_TASK_TIMEOUT
115
+ task_event_timeout: Optional[int] = DEFAULT_TASK_EVENT_TIMEOUT
116
+ wellknown_path: Optional[str] = DEFAULT_WELLKNOWN_PATH
117
+
118
+ @field_validator("registry", mode="before")
119
+ @classmethod
120
+ def normalize_registry(cls, v):
121
+ """Normalize registry to list format."""
122
+ if v is None:
123
+ return None
124
+ if isinstance(v, list):
125
+ return v
126
+ # Single registry instance -> convert to list
127
+ return [v]
128
+
129
+ model_config = ConfigDict(
130
+ arbitrary_types_allowed=True,
131
+ extra="allow",
132
+ )
14
133
 
15
134
 
16
135
  class A2AFastAPIDefaultAdapter(ProtocolAdapter):
17
- def __init__(self, agent_name, agent_description, **kwargs):
136
+ """Default A2A protocol adapter for FastAPI applications.
137
+
138
+ Provides comprehensive configuration options for A2A protocol including
139
+ agent card settings, task timeouts, wellknown endpoints, and transport
140
+ configurations. All configuration items have sensible defaults but can
141
+ be overridden by users.
142
+ """
143
+
144
+ def __init__(
145
+ self,
146
+ agent_name: str,
147
+ agent_description: str,
148
+ a2a_config: Optional[AgentCardWithRuntimeConfig] = None,
149
+ **kwargs: Any,
150
+ ) -> None:
151
+ """Initialize A2A protocol adapter.
152
+
153
+ Args:
154
+ agent_name: Agent name
155
+ (fallback if not in a2a_config.agent_card)
156
+ agent_description: Agent description
157
+ (fallback if not in a2a_config.agent_card)
158
+ a2a_config: Runtime configuration with AgentCard and runtime
159
+ settings
160
+ **kwargs: Additional arguments for parent class
161
+ """
18
162
  super().__init__(**kwargs)
19
- self._agent_name = agent_name
20
- self._agent_description = agent_description
21
163
  self._json_rpc_path = kwargs.get("json_rpc_path", A2A_JSON_RPC_URL)
22
- self._base_url = kwargs.get("base_url")
23
164
 
24
- def add_endpoint(self, app, func: Callable, **kwargs):
165
+ if a2a_config is None:
166
+ a2a_config = AgentCardWithRuntimeConfig()
167
+ self._a2a_config = a2a_config
168
+
169
+ # Extract name/description from agent_card, fallback to parameters
170
+ agent_card_name = None
171
+ agent_card_description = None
172
+ if a2a_config.agent_card is not None:
173
+ if isinstance(a2a_config.agent_card, dict):
174
+ agent_card_name = a2a_config.agent_card.get("name")
175
+ agent_card_description = a2a_config.agent_card.get(
176
+ "description",
177
+ )
178
+ elif isinstance(a2a_config.agent_card, AgentCard):
179
+ agent_card_name = getattr(a2a_config.agent_card, "name", None)
180
+ agent_card_description = getattr(
181
+ a2a_config.agent_card,
182
+ "description",
183
+ None,
184
+ )
185
+
186
+ self._agent_name = (
187
+ agent_card_name if agent_card_name is not None else agent_name
188
+ )
189
+ self._agent_description = (
190
+ agent_card_description
191
+ if agent_card_description is not None
192
+ else agent_description
193
+ )
194
+ self._host = a2a_config.host or get_first_non_loopback_ip()
195
+ self._port = a2a_config.port
196
+
197
+ # Normalize registry to list
198
+ registry = a2a_config.registry
199
+ if registry is None:
200
+ self._registry: List[A2ARegistry] = []
201
+ elif isinstance(registry, A2ARegistry):
202
+ self._registry = [registry]
203
+ elif isinstance(registry, list):
204
+ if not all(isinstance(r, A2ARegistry) for r in registry):
205
+ error_msg = (
206
+ "[A2A] Invalid registry list: all items must be "
207
+ "A2ARegistry instances"
208
+ )
209
+ logger.error(error_msg)
210
+ raise TypeError(error_msg)
211
+ self._registry = registry
212
+
213
+ self._task_timeout = a2a_config.task_timeout or DEFAULT_TASK_TIMEOUT
214
+ self._task_event_timeout = (
215
+ a2a_config.task_event_timeout or DEFAULT_TASK_EVENT_TIMEOUT
216
+ )
217
+ self._wellknown_path = (
218
+ a2a_config.wellknown_path or DEFAULT_WELLKNOWN_PATH
219
+ )
220
+
221
+ def add_endpoint(
222
+ self,
223
+ app: FastAPI,
224
+ func: Callable,
225
+ **kwargs: Any,
226
+ ) -> None:
227
+ """Add A2A protocol endpoints to FastAPI application.
228
+
229
+ Args:
230
+ app: FastAPI application instance
231
+ func: Agent execution function
232
+ **kwargs: Additional arguments for registry registration
233
+ """
25
234
  request_handler = DefaultRequestHandler(
26
235
  agent_executor=A2AExecutor(func=func),
27
236
  task_store=InMemoryTaskStore(),
28
237
  )
29
238
 
30
- agent_card = self.get_agent_card(
31
- agent_name=self._agent_name,
32
- agent_description=self._agent_description,
33
- )
239
+ agent_card = self.get_agent_card(app=app)
34
240
 
35
241
  server = A2AFastAPIApplication(
36
242
  agent_card=agent_card,
37
243
  http_handler=request_handler,
38
244
  )
39
245
 
40
- server.add_routes_to_app(app, rpc_url=self._json_rpc_path)
246
+ server.add_routes_to_app(
247
+ app,
248
+ rpc_url=self._json_rpc_path,
249
+ agent_card_url=self._wellknown_path,
250
+ )
251
+
252
+ if self._registry:
253
+ self._register_with_all_registries(
254
+ agent_card=agent_card,
255
+ app=app,
256
+ )
257
+
258
+ def _register_with_all_registries(
259
+ self,
260
+ agent_card: AgentCard,
261
+ app: FastAPI,
262
+ ) -> None:
263
+ """Register agent with all configured registry instances.
264
+
265
+ Registration failures are logged but do not block startup.
266
+
267
+ Args:
268
+ agent_card: The generated AgentCard
269
+ app: FastAPI application instance
270
+ """
271
+ a2a_transports_properties = self._build_a2a_transports_properties(
272
+ app=app,
273
+ )
274
+
275
+ for registry in self._registry:
276
+ registry_name = registry.registry_name()
277
+ try:
278
+ logger.info(
279
+ "[A2A] Registering with registry: %s",
280
+ registry_name,
281
+ )
282
+ registry.register(
283
+ agent_card=agent_card,
284
+ a2a_transports_properties=a2a_transports_properties,
285
+ )
286
+ logger.info(
287
+ "[A2A] Successfully registered with registry: %s",
288
+ registry_name,
289
+ )
290
+ except Exception as e:
291
+ logger.warning(
292
+ "[A2A] Failed to register with registry %s: %s. "
293
+ "This will not block runtime startup.",
294
+ registry_name,
295
+ str(e),
296
+ exc_info=True,
297
+ )
298
+
299
+ def _build_a2a_transports_properties(
300
+ self,
301
+ app: FastAPI,
302
+ ) -> List[A2ATransportsProperties]:
303
+ """Build A2ATransportsProperties from runtime configuration.
41
304
 
42
- def _get_json_rpc_url(self) -> str:
43
- base = self._base_url or "http://127.0.0.1:8000"
44
- return posixpath.join(
45
- base.rstrip("/"),
305
+ Args:
306
+ app: FastAPI application instance
307
+
308
+ Returns:
309
+ List of A2ATransportsProperties instances
310
+ """
311
+ transports_list = []
312
+
313
+ path = getattr(app, "root_path", "")
314
+ json_rpc = urljoin(
315
+ path.rstrip("/") + "/",
46
316
  self._json_rpc_path.lstrip("/"),
47
317
  )
48
318
 
319
+ default_transport = A2ATransportsProperties(
320
+ host=self._host,
321
+ port=self._port,
322
+ path=json_rpc,
323
+ support_tls=False,
324
+ extra={},
325
+ transport_type=DEFAULT_TRANSPORT,
326
+ )
327
+ transports_list.append(default_transport)
328
+
329
+ return transports_list
330
+
331
+ def _get_agent_card_field(
332
+ self,
333
+ field_name: str,
334
+ default: Any = None,
335
+ ) -> Any:
336
+ """Extract field from agent_card (dict or AgentCard object).
337
+
338
+ Args:
339
+ field_name: Field name to retrieve
340
+ default: Default value if not found
341
+
342
+ Returns:
343
+ Field value or default
344
+ """
345
+ agent_card = self._a2a_config.agent_card
346
+ if agent_card is None:
347
+ return default
348
+
349
+ if isinstance(agent_card, dict):
350
+ return agent_card.get(field_name, default)
351
+ else:
352
+ # AgentCard object
353
+ return getattr(agent_card, field_name, default)
354
+
49
355
  def get_agent_card(
50
356
  self,
51
- agent_name: str,
52
- agent_description: str,
357
+ app: Optional[FastAPI] = None, # pylint: disable=unused-argument
53
358
  ) -> AgentCard:
54
- capabilities = AgentCapabilities(
55
- streaming=False,
56
- push_notifications=False,
359
+ """Build AgentCard from configuration.
360
+
361
+ Constructs AgentCard from agent_card field (dict or AgentCard),
362
+ filling missing fields with defaults and computed values.
363
+
364
+ Args:
365
+ app: FastAPI app instance (for URL generation)
366
+
367
+ Returns:
368
+ Configured AgentCard instance
369
+ """
370
+
371
+ # Generate URL if not provided
372
+ url = self._get_agent_card_field("url")
373
+ if url is None:
374
+ path = getattr(app, "root_path", "")
375
+ json_rpc = urljoin(
376
+ path.rstrip("/") + "/",
377
+ self._json_rpc_path.lstrip("/"),
378
+ ).lstrip("/")
379
+ base_url = (
380
+ f"{self._host}:{self._port}"
381
+ if self._host.startswith(("http://", "https://"))
382
+ else f"http://{self._host}:{self._port}"
383
+ )
384
+ url = f"{base_url}/{json_rpc}"
385
+
386
+ # Initialize from agent_card
387
+ card_kwargs = {}
388
+
389
+ # Set required fields
390
+ card_kwargs["name"] = self._get_agent_card_field(
391
+ "name",
392
+ self._agent_name,
57
393
  )
58
- skill = AgentSkill(
59
- id="dialog",
60
- name="Natural Language Dialog Skill",
61
- description="Enables natural language conversation and dialogue "
62
- "with users",
63
- tags=["natural language", "dialog", "conversation"],
64
- examples=[
65
- "Hello, how are you?",
66
- "Can you help me with something?",
67
- ],
394
+ card_kwargs["description"] = self._get_agent_card_field(
395
+ "description",
396
+ self._agent_description,
397
+ )
398
+ card_kwargs["url"] = url
399
+ card_kwargs["version"] = self._get_agent_card_field(
400
+ "version",
401
+ AGENT_VERSION,
68
402
  )
69
403
 
70
- return AgentCard(
71
- capabilities=capabilities,
72
- skills=[skill],
73
- name=agent_name,
74
- description=agent_description,
75
- default_input_modes=["text"],
76
- default_output_modes=["text"],
77
- url=self._get_json_rpc_url(),
78
- version="1.0.0",
404
+ # Set defaults for required fields
405
+ card_kwargs["preferred_transport"] = self._get_agent_card_field(
406
+ "preferred_transport",
407
+ DEFAULT_TRANSPORT,
408
+ )
409
+ card_kwargs["additional_interfaces"] = self._get_agent_card_field(
410
+ "additional_interfaces",
411
+ [],
412
+ )
413
+ card_kwargs["default_input_modes"] = self._get_agent_card_field(
414
+ "default_input_modes",
415
+ DEFAULT_INPUT_OUTPUT_MODES,
416
+ )
417
+ card_kwargs["default_output_modes"] = self._get_agent_card_field(
418
+ "default_output_modes",
419
+ DEFAULT_INPUT_OUTPUT_MODES,
79
420
  )
421
+ card_kwargs["skills"] = self._get_agent_card_field(
422
+ "skills",
423
+ [
424
+ AgentSkill(
425
+ id="dialog",
426
+ name="Natural Language Dialog Skill",
427
+ description=(
428
+ "Enables natural language conversation and dialogue "
429
+ "with users"
430
+ ),
431
+ tags=["natural language", "dialog", "conversation"],
432
+ examples=[
433
+ "Hello, how are you?",
434
+ "Can you help me with something?",
435
+ ],
436
+ ),
437
+ ],
438
+ )
439
+ # Runtime-managed AgentCard fields: user values are ignored
440
+ if self._get_agent_card_field("capabilities") is not None:
441
+ logger.warning(
442
+ "[A2A] Ignoring user-provided AgentCard.capabilities; "
443
+ "runtime controls this field.",
444
+ )
445
+ card_kwargs["capabilities"] = AgentCapabilities(
446
+ streaming=False,
447
+ push_notifications=False,
448
+ state_transition_history=False,
449
+ )
450
+
451
+ if self._get_agent_card_field("protocol_version") is not None:
452
+ logger.warning(
453
+ "[A2A] Ignoring user-provided AgentCard.protocol_version; "
454
+ "runtime controls this field.",
455
+ )
456
+
457
+ if (
458
+ self._get_agent_card_field(
459
+ "supports_authenticated_extended_card",
460
+ )
461
+ is not None
462
+ ):
463
+ logger.warning(
464
+ "[A2A] Ignoring user-provided "
465
+ "AgentCard.supports_authenticated_extended_card; "
466
+ "runtime controls this field.",
467
+ )
468
+
469
+ if self._get_agent_card_field("signatures") is not None:
470
+ logger.warning(
471
+ "[A2A] Ignoring user-provided AgentCard.signatures; "
472
+ "runtime controls this field.",
473
+ )
474
+
475
+ # Add optional fields
476
+ for field in [
477
+ "provider",
478
+ "documentation_url",
479
+ "icon_url",
480
+ "security_schemes",
481
+ "security",
482
+ ]:
483
+ value = self._get_agent_card_field(field)
484
+ if value is None:
485
+ continue
486
+ # Backward compatibility: allow simple string provider and map it
487
+ # to AgentProvider.organization
488
+ if field == "provider" and isinstance(value, str):
489
+ card_kwargs[field] = {
490
+ "organization": value,
491
+ "url": url,
492
+ }
493
+ else:
494
+ card_kwargs[field] = value
495
+
496
+ return AgentCard(**card_kwargs)
@@ -0,0 +1,76 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ A2A Registry Extension Point
4
+
5
+ Defines the abstract interface for A2A registry implementations.
6
+ Registry implementations are responsible for registering agent services
7
+ to service discovery systems (for example: Nacos).
8
+ """
9
+ import logging
10
+ from abc import ABC, abstractmethod
11
+ from dataclasses import dataclass, field
12
+ from typing import Any, Dict, List, Optional
13
+
14
+ from a2a.types import AgentCard
15
+
16
+ __all__ = [
17
+ "A2ARegistry",
18
+ "A2ATransportsProperties",
19
+ ]
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+
24
+ @dataclass
25
+ class A2ATransportsProperties:
26
+ """A2A transport properties for multi-transport support.
27
+
28
+ Attributes:
29
+ host: Transport host
30
+ port: Transport port
31
+ path: Transport path
32
+ support_tls: Whether TLS is supported
33
+ extra: Additional transport properties
34
+ transport_type: Type of transport (e.g., "JSONRPC", "HTTP")
35
+ """
36
+
37
+ host: Optional[str] = None
38
+ port: Optional[int] = None
39
+ path: Optional[str] = None
40
+ support_tls: Optional[bool] = False
41
+ extra: Dict[str, Any] = field(default_factory=dict)
42
+ transport_type: str = "JSONRPC"
43
+
44
+
45
+ class A2ARegistry(ABC):
46
+ """Abstract base class for A2A registry implementations.
47
+
48
+ Implementations should not raise on non-fatal errors during startup; the
49
+ runtime will catch and log exceptions so that registry failures do not
50
+ prevent the runtime from starting.
51
+ """
52
+
53
+ @abstractmethod
54
+ def registry_name(self) -> str:
55
+ """Return a short name identifying the registry (e.g. "nacos")."""
56
+ raise NotImplementedError("Subclasses must implement registry_name()")
57
+
58
+ @abstractmethod
59
+ def register(
60
+ self,
61
+ agent_card: AgentCard,
62
+ a2a_transports_properties: Optional[
63
+ List[A2ATransportsProperties]
64
+ ] = None,
65
+ ) -> None:
66
+ """Register an agent/service.
67
+
68
+ Args:
69
+ agent_card: Agent card of this agent
70
+ a2a_transports_properties: Multiple transports for A2A Server,
71
+ and each transport might include different configs.
72
+
73
+ Implementations may register the agent card itself and/or endpoint
74
+ depending on their semantics.
75
+ """
76
+ raise NotImplementedError("Subclasses must implement register()")