agentscope-runtime 1.0.3__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 (49) 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 +371 -0
  7. agentscope_runtime/common/container_clients/knative_client.py +466 -0
  8. agentscope_runtime/engine/__init__.py +4 -0
  9. agentscope_runtime/engine/constant.py +1 -0
  10. agentscope_runtime/engine/deployers/__init__.py +12 -0
  11. agentscope_runtime/engine/deployers/adapter/a2a/__init__.py +26 -51
  12. agentscope_runtime/engine/deployers/adapter/a2a/a2a_protocol_adapter.py +19 -10
  13. agentscope_runtime/engine/deployers/adapter/a2a/a2a_registry.py +4 -201
  14. agentscope_runtime/engine/deployers/adapter/a2a/nacos_a2a_registry.py +134 -25
  15. agentscope_runtime/engine/deployers/agentrun_deployer.py +2 -2
  16. agentscope_runtime/engine/deployers/fc_deployer.py +1506 -0
  17. agentscope_runtime/engine/deployers/knative_deployer.py +290 -0
  18. agentscope_runtime/engine/runner.py +12 -0
  19. agentscope_runtime/engine/services/agent_state/redis_state_service.py +2 -2
  20. agentscope_runtime/engine/services/memory/redis_memory_service.py +2 -2
  21. agentscope_runtime/engine/services/session_history/redis_session_history_service.py +2 -2
  22. agentscope_runtime/engine/tracing/wrapper.py +18 -4
  23. agentscope_runtime/sandbox/__init__.py +14 -6
  24. agentscope_runtime/sandbox/box/base/__init__.py +2 -2
  25. agentscope_runtime/sandbox/box/base/base_sandbox.py +51 -1
  26. agentscope_runtime/sandbox/box/browser/__init__.py +2 -2
  27. agentscope_runtime/sandbox/box/browser/browser_sandbox.py +198 -2
  28. agentscope_runtime/sandbox/box/filesystem/__init__.py +2 -2
  29. agentscope_runtime/sandbox/box/filesystem/filesystem_sandbox.py +99 -2
  30. agentscope_runtime/sandbox/box/gui/__init__.py +2 -2
  31. agentscope_runtime/sandbox/box/gui/gui_sandbox.py +117 -1
  32. agentscope_runtime/sandbox/box/mobile/__init__.py +2 -2
  33. agentscope_runtime/sandbox/box/mobile/mobile_sandbox.py +247 -100
  34. agentscope_runtime/sandbox/box/sandbox.py +98 -65
  35. agentscope_runtime/sandbox/box/shared/routers/generic.py +36 -29
  36. agentscope_runtime/sandbox/client/__init__.py +6 -1
  37. agentscope_runtime/sandbox/client/async_http_client.py +339 -0
  38. agentscope_runtime/sandbox/client/base.py +74 -0
  39. agentscope_runtime/sandbox/client/http_client.py +108 -329
  40. agentscope_runtime/sandbox/enums.py +7 -0
  41. agentscope_runtime/sandbox/manager/sandbox_manager.py +264 -4
  42. agentscope_runtime/sandbox/manager/server/app.py +7 -1
  43. agentscope_runtime/version.py +1 -1
  44. {agentscope_runtime-1.0.3.dist-info → agentscope_runtime-1.0.4.dist-info}/METADATA +102 -28
  45. {agentscope_runtime-1.0.3.dist-info → agentscope_runtime-1.0.4.dist-info}/RECORD +49 -40
  46. {agentscope_runtime-1.0.3.dist-info → agentscope_runtime-1.0.4.dist-info}/WHEEL +0 -0
  47. {agentscope_runtime-1.0.3.dist-info → agentscope_runtime-1.0.4.dist-info}/entry_points.txt +0 -0
  48. {agentscope_runtime-1.0.3.dist-info → agentscope_runtime-1.0.4.dist-info}/licenses/LICENSE +0 -0
  49. {agentscope_runtime-1.0.3.dist-info → agentscope_runtime-1.0.4.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,466 @@
1
+ # -*- coding: utf-8 -*-
2
+ # pylint: disable=too-many-branches,too-many-statements
3
+
4
+ import logging
5
+ import time
6
+ import traceback
7
+ from typing import Optional, Dict, Tuple
8
+ from kubernetes import client
9
+ from kubernetes import config as k8s_config
10
+ from kubernetes.client.rest import ApiException
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ class KnativeClient:
16
+ """
17
+ A client for interacting with Knative Services in a Kubernetes cluster.
18
+
19
+ This client wraps the Kubernetes CustomObjectsApi to manage
20
+ Knative Service resources (serving.knative.dev/v1).
21
+ """
22
+
23
+ def __init__(
24
+ self,
25
+ config=None,
26
+ image_registry: Optional[str] = None,
27
+ ):
28
+ self.config = config
29
+ namespace = self.config.k8s_namespace
30
+ kubeconfig = self.config.kubeconfig_path
31
+ self.image_registry = image_registry
32
+ try:
33
+ if kubeconfig:
34
+ k8s_config.load_kube_config(config_file=kubeconfig)
35
+ else:
36
+ # Try to load in-cluster config first, then fall back to
37
+ # kubeconfig
38
+ try:
39
+ k8s_config.load_incluster_config()
40
+ except k8s_config.ConfigException:
41
+ k8s_config.load_kube_config()
42
+ self._custom_api = client.CustomObjectsApi() # For Knative Service
43
+ self.v1 = client.CoreV1Api()
44
+ self.namespace = namespace
45
+ # Test connection
46
+ self.v1.list_namespace()
47
+ logger.debug("Kubernetes client initialized successfully")
48
+ except Exception as e:
49
+ raise RuntimeError(
50
+ f"Kubernetes client initialization failed: {str(e)}\n"
51
+ "Solutions:\n"
52
+ "• Ensure kubectl is configured\n"
53
+ "• Check kubeconfig file permissions\n"
54
+ "• Verify cluster connectivity\n"
55
+ "• For in-cluster: ensure proper RBAC permissions",
56
+ ) from e
57
+
58
+ def _is_local_cluster(self):
59
+ """
60
+ Determine if we're connected to a local Kubernetes cluster.
61
+
62
+ Returns:
63
+ bool: True if connected to a local cluster, False otherwise
64
+ """
65
+ try:
66
+ # Get the current context configuration
67
+ contexts, current_context = k8s_config.list_kube_config_contexts(
68
+ config_file=self.config.kubeconfig_path
69
+ if hasattr(self.config, "kubeconfig_path")
70
+ and self.config.kubeconfig_path
71
+ else None,
72
+ )
73
+
74
+ if current_context and current_context.get("context"):
75
+ cluster_name = current_context["context"].get("cluster", "")
76
+ server = None
77
+
78
+ # Get cluster server URL
79
+ for cluster in contexts.get("clusters", []):
80
+ if cluster["name"] == cluster_name:
81
+ server = cluster.get("cluster", {}).get("server", "")
82
+ break
83
+
84
+ if server:
85
+ # Check for common local cluster patterns
86
+ local_patterns = [
87
+ "localhost",
88
+ "127.0.0.1",
89
+ "0.0.0.0",
90
+ "docker-desktop",
91
+ "kind-", # kind clusters
92
+ "minikube", # minikube
93
+ "k3d-", # k3d clusters
94
+ "colima", # colima
95
+ ]
96
+
97
+ server_lower = server.lower()
98
+ cluster_lower = cluster_name.lower()
99
+
100
+ for pattern in local_patterns:
101
+ if pattern in server_lower or pattern in cluster_lower:
102
+ return True
103
+
104
+ return False
105
+
106
+ except Exception as e:
107
+ logger.debug(
108
+ f"Could not determine cluster type, assuming remote: {e}",
109
+ )
110
+ # If we can't determine, assume remote for safety
111
+ return False
112
+
113
+ def create_kservice(
114
+ self,
115
+ name: str,
116
+ image: str,
117
+ ports=None,
118
+ volumes=None,
119
+ environment=None,
120
+ runtime_config=None,
121
+ annotations: Optional[Dict[str, str]] = None,
122
+ labels: Optional[Dict[str, str]] = None,
123
+ namespace: Optional[str] = None,
124
+ ) -> Tuple[str, str] | Tuple[None, None]:
125
+ """
126
+ Create a Knative Service.
127
+
128
+ Args:
129
+ name (str): Name of the KService.
130
+ image (str): Container image.
131
+ ports: List of ports to expose
132
+ volumes: Volume mounts dictionary
133
+ environment: Environment variables dictionary
134
+ runtime_config: Runtime configuration dictionary
135
+ annotations (dict): KService annotations.
136
+ labels (dict): KService labels.
137
+ namespace (str): Override default namespace.
138
+
139
+ Returns:
140
+ dict: Created Knative Service object.
141
+ """
142
+ ns = namespace or self.namespace
143
+
144
+ # Create kservice pod specification
145
+ pod_spec = self._create_kservice_podspec(
146
+ image,
147
+ name,
148
+ ports,
149
+ volumes,
150
+ environment,
151
+ runtime_config,
152
+ )
153
+
154
+ kservice_manifest = {
155
+ "apiVersion": "serving.knative.dev/v1",
156
+ "kind": "Service",
157
+ "metadata": {
158
+ "name": name,
159
+ "namespace": ns,
160
+ "labels": labels or {},
161
+ "annotations": annotations or {},
162
+ },
163
+ "spec": {
164
+ "template": {
165
+ "metadata": {
166
+ "labels": labels or {},
167
+ "annotations": annotations or {},
168
+ },
169
+ "spec": pod_spec,
170
+ },
171
+ },
172
+ }
173
+
174
+ logger.info(f"Creating Knative Service '{name}' in namespace '{ns}'")
175
+ try:
176
+ self._custom_api.create_namespaced_custom_object(
177
+ group="serving.knative.dev",
178
+ version="v1",
179
+ namespace=ns,
180
+ plural="services",
181
+ body=kservice_manifest,
182
+ )
183
+ logger.debug("Knative Service created successfully.")
184
+ # Wait for kservice to be ready
185
+ if not self.wait_for_ready(name, timeout=120):
186
+ logger.warning(f"KService '{name}' may not be fully ready")
187
+ ksvc = self.get_kservice(name, ns)
188
+
189
+ url = ksvc.get("status", {}).get("url")
190
+ return (
191
+ name,
192
+ url or "",
193
+ )
194
+ except ApiException as e:
195
+ logger.error(
196
+ f"Failed to create KService: {e}, {traceback.format_exc()}",
197
+ )
198
+ return None, None
199
+
200
+ def _create_kservice_podspec(
201
+ self,
202
+ image,
203
+ name,
204
+ ports=None,
205
+ volumes=None,
206
+ environment=None,
207
+ runtime_config=None,
208
+ ):
209
+ """Create a Knative Service Pod specification."""
210
+ if runtime_config is None:
211
+ runtime_config = {}
212
+
213
+ container_name = name or "main-container"
214
+
215
+ # Use image registry if configured
216
+ if not self.image_registry:
217
+ full_image = image
218
+ else:
219
+ full_image = f"{self.image_registry}/{image}"
220
+
221
+ # Create container spec (reuse the existing pod spec logic)
222
+ container = client.V1Container(
223
+ name=container_name,
224
+ image=full_image,
225
+ image_pull_policy=runtime_config.get(
226
+ "image_pull_policy",
227
+ "IfNotPresent",
228
+ ),
229
+ )
230
+
231
+ # Configure ports
232
+ if ports:
233
+ container_ports = []
234
+ for port_spec in ports:
235
+ port_info = self._parse_port_spec(port_spec)
236
+ if port_info:
237
+ container_ports.append(
238
+ client.V1ContainerPort(
239
+ container_port=port_info["port"],
240
+ protocol=port_info["protocol"],
241
+ ),
242
+ )
243
+ if container_ports:
244
+ container.ports = container_ports
245
+
246
+ # Configure environment variables
247
+ if environment:
248
+ env_vars = []
249
+ for key, value in environment.items():
250
+ env_vars.append(client.V1EnvVar(name=key, value=str(value)))
251
+ container.env = env_vars
252
+
253
+ # Configure volume mounts and volumes
254
+ volume_mounts = []
255
+ pod_volumes = []
256
+ if volumes:
257
+ for volume_idx, (host_path, mount_info) in enumerate(
258
+ volumes.items(),
259
+ ):
260
+ if isinstance(mount_info, dict):
261
+ container_path = mount_info["bind"]
262
+ mode = mount_info.get("mode", "rw")
263
+ else:
264
+ container_path = mount_info
265
+ mode = "rw"
266
+ volume_name = f"vol-{volume_idx}"
267
+
268
+ # Create volume mount
269
+ volume_mounts.append(
270
+ client.V1VolumeMount(
271
+ name=volume_name,
272
+ mount_path=container_path,
273
+ read_only=(mode == "ro"),
274
+ ),
275
+ )
276
+ # Create host path volume
277
+ pod_volumes.append(
278
+ client.V1Volume(
279
+ name=volume_name,
280
+ host_path=client.V1HostPathVolumeSource(
281
+ path=host_path,
282
+ ),
283
+ ),
284
+ )
285
+
286
+ if volume_mounts:
287
+ container.volume_mounts = volume_mounts
288
+
289
+ # Apply runtime config to container
290
+ if "resources" in runtime_config:
291
+ container.resources = client.V1ResourceRequirements(
292
+ **runtime_config["resources"],
293
+ )
294
+
295
+ if "security_context" in runtime_config:
296
+ container.security_context = client.V1SecurityContext(
297
+ **runtime_config["security_context"],
298
+ )
299
+
300
+ # Pod template specification
301
+ pod_spec = client.V1PodSpec(
302
+ containers=[container],
303
+ restart_policy=runtime_config.get(
304
+ "restart_policy",
305
+ "Always",
306
+ ), # KService typically use Always
307
+ )
308
+
309
+ if pod_volumes:
310
+ pod_spec.volumes = pod_volumes
311
+
312
+ if "node_selector" in runtime_config:
313
+ pod_spec.node_selector = runtime_config["node_selector"]
314
+
315
+ if "tolerations" in runtime_config:
316
+ pod_spec.tolerations = runtime_config["tolerations"]
317
+
318
+ # Handle image pull secrets
319
+ image_pull_secrets = runtime_config.get("image_pull_secrets", [])
320
+ if image_pull_secrets:
321
+ secrets = []
322
+ for secret_name in image_pull_secrets:
323
+ secrets.append(client.V1LocalObjectReference(name=secret_name))
324
+ pod_spec.image_pull_secrets = secrets
325
+
326
+ return pod_spec
327
+
328
+ def _parse_port_spec(self, port_spec):
329
+ """
330
+ Parse port specification.
331
+ - "80/tcp" -> {"port": 80, "protocol": "TCP"}
332
+ - "80" -> {"port": 80, "protocol": "TCP"}
333
+ - 80 -> {"port": 80, "protocol": "TCP"}
334
+ """
335
+ try:
336
+ if isinstance(port_spec, int):
337
+ return {"port": port_spec, "protocol": "TCP"}
338
+
339
+ if isinstance(port_spec, str):
340
+ if "/" in port_spec:
341
+ port_str, protocol = port_spec.split("/", 1)
342
+ else:
343
+ port_str = port_spec
344
+ protocol = "TCP"
345
+
346
+ port = int(port_str)
347
+ protocol = protocol.upper()
348
+
349
+ return {"port": port, "protocol": protocol}
350
+
351
+ # Log a warning if the port_spec is neither int nor str
352
+ logger.warning(f"Unsupported port specification: {port_spec}")
353
+ return None
354
+
355
+ except ValueError as e:
356
+ logger.error(f"Failed to parse port spec '{port_spec}': {e}")
357
+ return None
358
+
359
+ def delete_kservice(
360
+ self,
361
+ name: str,
362
+ namespace: Optional[str] = None,
363
+ ) -> bool:
364
+ """Delete a Knative Service."""
365
+ ns = namespace or self.namespace
366
+ logger.info(f"Deleting Knative Service '{name}' in namespace '{ns}'")
367
+ try:
368
+ self._custom_api.delete_namespaced_custom_object(
369
+ group="serving.knative.dev",
370
+ version="v1",
371
+ namespace=ns,
372
+ plural="services",
373
+ name=name,
374
+ body=client.V1DeleteOptions(),
375
+ )
376
+ logger.debug("Knative Service deleted.")
377
+ return True
378
+ except ApiException as e:
379
+ if e.status == 404:
380
+ logger.warning(f"Knative Service '{name}' not found.")
381
+ return False
382
+ logger.error(f"Failed to delete Knative Service: {e.body}")
383
+ raise
384
+
385
+ def get_kservice(self, name: str, namespace: Optional[str] = None):
386
+ """Get a Knative Service by name."""
387
+ try:
388
+ svc = self._custom_api.get_namespaced_custom_object(
389
+ group="serving.knative.dev",
390
+ version="v1",
391
+ namespace=namespace,
392
+ plural="services",
393
+ name=name,
394
+ )
395
+ return svc
396
+ except ApiException as e:
397
+ if e.status == 404:
398
+ return None
399
+ raise
400
+
401
+ def wait_for_ready(
402
+ self,
403
+ name: str,
404
+ timeout: int = 300,
405
+ poll_interval: int = 5,
406
+ ) -> bool:
407
+ """
408
+ Wait until the Knative Service is ready.
409
+
410
+ Returns:
411
+ bool: True if ready within timeout, False otherwise.
412
+ """
413
+ logger.info(
414
+ f"Waiting for Knative Service '{name}' "
415
+ "to become ready (timeout={timeout}s)",
416
+ )
417
+
418
+ start_time = time.time()
419
+ while time.time() - start_time < timeout:
420
+ svc = self.get_kservice(name, self.namespace)
421
+ if svc:
422
+ conditions = svc.get("status", {}).get("conditions", [])
423
+ ready_cond = next(
424
+ (c for c in conditions if c.get("type") == "Ready"),
425
+ None,
426
+ )
427
+ if ready_cond and ready_cond.get("status") == "True":
428
+ logger.info(f"Knative Service '{name}' is ready.")
429
+ return True
430
+ time.sleep(poll_interval)
431
+
432
+ logger.error(
433
+ f"Knative Service '{name}' did not "
434
+ "become ready within {timeout} seconds.",
435
+ )
436
+ return False
437
+
438
+ def get_kservice_status(self, name):
439
+ """Get the current status of the specified kservice."""
440
+ try:
441
+ ksvc = self.get_kservice(name, self.namespace)
442
+
443
+ return {
444
+ "name": name,
445
+ "url": ksvc.get("status", {}).get("url"),
446
+ "conditions": [
447
+ {
448
+ "type": condition.get("type"),
449
+ "status": condition.get("status"),
450
+ "reason": condition.get("reason"),
451
+ "message": condition.get("message"),
452
+ }
453
+ for condition in (
454
+ ksvc.get("status", {}).get("conditions", [])
455
+ )
456
+ ],
457
+ }
458
+ except ApiException as e:
459
+ if e.status == 404:
460
+ logger.warning(f"KService '{name}' not found")
461
+ else:
462
+ logger.error(f"Failed to get ksvc status: {e.reason}")
463
+ return None
464
+ except Exception as e:
465
+ logger.error(f"An error occurred: {e}, {traceback.format_exc()}")
466
+ return None
@@ -11,8 +11,10 @@ if TYPE_CHECKING:
11
11
  DeployManager,
12
12
  LocalDeployManager,
13
13
  KubernetesDeployManager,
14
+ KnativeDeployManager,
14
15
  ModelstudioDeployManager,
15
16
  AgentRunDeployManager,
17
+ FCDeployManager,
16
18
  )
17
19
 
18
20
 
@@ -22,7 +24,9 @@ install_lazy_loader(
22
24
  "DeployManager": ".deployers",
23
25
  "LocalDeployManager": ".deployers",
24
26
  "KubernetesDeployManager": ".deployers",
27
+ "KnativeDeployManager": ".deployers",
25
28
  "ModelstudioDeployManager": ".deployers",
26
29
  "AgentRunDeployManager": ".deployers",
30
+ "FCDeployManager": ".deployers",
27
31
  },
28
32
  )
@@ -5,4 +5,5 @@ ALLOWED_FRAMEWORK_TYPES = [
5
5
  "autogen",
6
6
  "langgraph",
7
7
  "agno",
8
+ "ms_agent_framework",
8
9
  ]
@@ -8,6 +8,9 @@ from .kubernetes_deployer import (
8
8
  from .modelstudio_deployer import (
9
9
  ModelstudioDeployManager,
10
10
  )
11
+ from .knative_deployer import (
12
+ KnativeDeployManager,
13
+ )
11
14
 
12
15
  try:
13
16
  from .agentrun_deployer import (
@@ -16,10 +19,19 @@ try:
16
19
  except ImportError:
17
20
  AgentRunDeployManager = None # type: ignore
18
21
 
22
+ try:
23
+ from .fc_deployer import (
24
+ FCDeployManager,
25
+ )
26
+ except ImportError:
27
+ FCDeployManager = None # type: ignore
28
+
19
29
  __all__ = [
20
30
  "DeployManager",
21
31
  "LocalDeployManager",
22
32
  "KubernetesDeployManager",
23
33
  "ModelstudioDeployManager",
24
34
  "AgentRunDeployManager",
35
+ "KnativeDeployManager",
36
+ "FCDeployManager",
25
37
  ]
@@ -1,57 +1,32 @@
1
1
  # -*- coding: utf-8 -*-
2
- from .a2a_protocol_adapter import (
3
- A2AFastAPIDefaultAdapter,
4
- AgentCardWithRuntimeConfig,
5
- extract_a2a_config,
6
- )
7
- from .a2a_registry import (
8
- A2ARegistry,
9
- A2ARegistrySettings,
10
- get_registry_settings,
11
- create_registry_from_env,
12
- )
2
+ from typing import TYPE_CHECKING
3
+ from .....common.utils.lazy_loader import install_lazy_loader
4
+
5
+ if TYPE_CHECKING:
6
+ from .a2a_protocol_adapter import (
7
+ A2AFastAPIDefaultAdapter,
8
+ AgentCardWithRuntimeConfig,
9
+ extract_a2a_config,
10
+ )
11
+ from .a2a_registry import A2ARegistry
12
+ from .nacos_a2a_registry import NacosRegistry
13
13
 
14
14
  # NOTE: NacosRegistry is NOT imported at module import time to avoid forcing
15
15
  # an optional dependency on environments that don't have nacos SDK installed.
16
- # Instead, NacosRegistry is imported lazily via __getattr__ (see below) when
16
+ # Instead, NacosRegistry is imported lazily via install_lazy_loader when
17
17
  # actually needed (e.g., when user does: from ... import NacosRegistry).
18
18
 
19
- __all__ = [
20
- "A2AFastAPIDefaultAdapter",
21
- "AgentCardWithRuntimeConfig",
22
- "extract_a2a_config",
23
- "A2ARegistry",
24
- "A2ARegistrySettings",
25
- "get_registry_settings",
26
- "create_registry_from_env",
27
- "NacosRegistry", # pylint: disable=undefined-all-variable
28
- ]
29
-
30
-
31
- def __getattr__(name: str):
32
- """
33
- Lazy import for NacosRegistry to avoid forcing optional nacos dependency.
34
-
35
- This function is called by Python when an attribute is accessed
36
- that doesn't exist at module level. This allows NacosRegistry
37
- to be imported only when actually needed, rather than at module
38
- import time.
39
-
40
- If the nacos SDK is not installed, provides a helpful error message
41
- instead of a confusing ImportError.
42
- """
43
- if name == "NacosRegistry":
44
- try:
45
- from .nacos_a2a_registry import NacosRegistry
46
-
47
- return NacosRegistry
48
- except ImportError as e:
49
- # Check if it's the v2.nacos dependency that's missing
50
- if "v2.nacos" in str(e) or "nacos" in str(e).lower():
51
- raise ImportError(
52
- "NacosRegistry requires the 'v2-nacos' package. "
53
- "Install it with: pip install v2-nacos",
54
- ) from e
55
- # Re-raise other import errors as-is
56
- raise
57
- raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
19
+ install_lazy_loader(
20
+ globals(),
21
+ {
22
+ "A2AFastAPIDefaultAdapter": ".a2a_protocol_adapter",
23
+ "AgentCardWithRuntimeConfig": ".a2a_protocol_adapter",
24
+ "extract_a2a_config": ".a2a_protocol_adapter",
25
+ "A2ARegistry": ".a2a_registry",
26
+ "NacosRegistry": {
27
+ "module": ".nacos_a2a_registry",
28
+ "hint": "NacosRegistry requires the 'nacos-sdk-python' package. "
29
+ "Install it with: pip install nacos-sdk-python",
30
+ },
31
+ },
32
+ )
@@ -19,19 +19,18 @@ from a2a.types import (
19
19
  AgentCard,
20
20
  AgentSkill,
21
21
  )
22
+ from a2a.utils import AGENT_CARD_WELL_KNOWN_PATH
22
23
  from fastapi import FastAPI
23
24
  from pydantic import ConfigDict, BaseModel, field_validator
24
25
 
25
26
  from agentscope_runtime.engine.deployers.utils.net_utils import (
26
27
  get_first_non_loopback_ip,
27
28
  )
28
- from agentscope_runtime.version import __version__ as runtime_version
29
29
 
30
30
  from .a2a_agent_adapter import A2AExecutor
31
31
  from .a2a_registry import (
32
32
  A2ARegistry,
33
33
  A2ATransportsProperties,
34
- create_registry_from_env,
35
34
  )
36
35
 
37
36
  # NOTE: Do NOT import NacosRegistry at module import time to avoid
@@ -44,7 +43,7 @@ from ..protocol_adapter import ProtocolAdapter
44
43
  logger = logging.getLogger(__name__)
45
44
 
46
45
  A2A_JSON_RPC_URL = "/a2a"
47
- DEFAULT_WELLKNOWN_PATH = "/.wellknown/agent-card.json"
46
+ DEFAULT_WELLKNOWN_PATH = AGENT_CARD_WELL_KNOWN_PATH
48
47
  DEFAULT_TASK_TIMEOUT = 60
49
48
  DEFAULT_TASK_EVENT_TIMEOUT = 10
50
49
  DEFAULT_TRANSPORT = "JSONRPC"
@@ -58,8 +57,12 @@ def extract_a2a_config(
58
57
  ) -> "AgentCardWithRuntimeConfig":
59
58
  """Normalize a2a_config to AgentCardWithRuntimeConfig object.
60
59
 
61
- Ensures a non-null ``AgentCardWithRuntimeConfig`` instance and sets up
62
- environment-based registry fallback if registry is not provided.
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)
63
66
 
64
67
  Args:
65
68
  a2a_config: Optional AgentCardWithRuntimeConfig instance.
@@ -70,12 +73,18 @@ def extract_a2a_config(
70
73
  if a2a_config is None:
71
74
  a2a_config = AgentCardWithRuntimeConfig()
72
75
 
73
- # Fallback to environment registry if not provided
76
+ # Try environment variables only if registry is not explicitly provided
74
77
  if a2a_config.registry is None:
75
- env_registry = create_registry_from_env()
76
- if env_registry is not None:
77
- a2a_config.registry = env_registry
78
- logger.debug("[A2A] Using registry from environment variables")
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")
79
88
 
80
89
  return a2a_config
81
90