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
@@ -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
  )
@@ -12,7 +12,11 @@ from pydantic import BaseModel
12
12
 
13
13
  from .base_app import BaseApp
14
14
  from ..deployers import DeployManager
15
- from ..deployers.adapter.a2a import A2AFastAPIDefaultAdapter
15
+ from ..deployers.adapter.a2a import (
16
+ A2AFastAPIDefaultAdapter,
17
+ AgentCardWithRuntimeConfig,
18
+ extract_a2a_config,
19
+ )
16
20
  from ..deployers.adapter.responses.response_api_protocol_adapter import (
17
21
  ResponseAPIDefaultAdapter,
18
22
  )
@@ -47,14 +51,49 @@ class AgentApp(BaseApp):
47
51
  backend_url: Optional[str] = None,
48
52
  runner: Optional[Runner] = None,
49
53
  enable_embedded_worker: bool = False,
54
+ a2a_config: Optional["AgentCardWithRuntimeConfig"] = None,
50
55
  **kwargs,
51
56
  ):
52
57
  """
53
58
  Initialize the AgentApp.
54
59
 
55
60
  Args:
56
- *args: Variable length argument list.
57
- **kwargs: Arbitrary keyword arguments.
61
+ app_name: Name of the agent application
62
+ app_description: Description of the agent application
63
+ endpoint_path: API endpoint path for processing requests
64
+ response_type: Type of response (default: "sse")
65
+ stream: Whether to enable streaming responses
66
+ request_model: Request model class
67
+ before_start: Callback function to execute before starting
68
+ after_finish: Callback function to execute after finishing
69
+ broker_url: URL for message broker
70
+ backend_url: URL for backend service
71
+ runner: Optional runner instance
72
+ enable_embedded_worker: Whether to enable embedded worker
73
+ a2a_config: Optional A2A runtime configuration.
74
+ Must be an ``AgentCardWithRuntimeConfig`` instance, which
75
+ contains ``agent_card`` (AgentCard object or dict) and runtime
76
+ settings (host, port, registry, task_timeout, etc.).
77
+ Example:
78
+ from a2a.types import AgentCard, AgentCapabilities
79
+ from agentscope_runtime.engine.deployers.adapter.a2a import ( # noqa: E501
80
+ AgentCardWithRuntimeConfig,
81
+ )
82
+ config = AgentCardWithRuntimeConfig(
83
+ agent_card={
84
+ "name": "MyAgent",
85
+ "version": "1.0.0",
86
+ "description": "My agent",
87
+ "url": "http://localhost:8080",
88
+ "capabilities": AgentCapabilities(),
89
+ "default_input_modes": ["text"],
90
+ "default_output_modes": ["text"],
91
+ "skills": [],
92
+ },
93
+ registry=[nacos_registry],
94
+ task_timeout=120,
95
+ )
96
+ **kwargs: Additional keyword arguments passed to FastAPI app
58
97
  """
59
98
 
60
99
  self.endpoint_path = endpoint_path
@@ -76,9 +115,13 @@ class AgentApp(BaseApp):
76
115
  self._shutdown_handler: Optional[Callable] = None
77
116
  self._framework_type: Optional[str] = None
78
117
 
118
+ # Prepare A2A protocol adapter configuration
119
+ a2a_config = extract_a2a_config(a2a_config=a2a_config)
120
+
79
121
  a2a_protocol = A2AFastAPIDefaultAdapter(
80
122
  agent_name=app_name,
81
123
  agent_description=app_description,
124
+ a2a_config=a2a_config,
82
125
  )
83
126
 
84
127
  response_protocol = ResponseAPIDefaultAdapter()
@@ -96,8 +139,8 @@ class AgentApp(BaseApp):
96
139
  backend_url=backend_url,
97
140
  )
98
141
 
99
- # Store custom endpoints and tasks for deployment
100
- # but don't add them to FastAPI here - let FastAPIAppFactory handle it
142
+ # Store custom endpoints for deployment
143
+ # FastAPIAppFactory will handle adding them to FastAPI
101
144
 
102
145
  def init(self, func):
103
146
  """Register init hook (support async and sync functions)."""
@@ -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,2 +1,32 @@
1
1
  # -*- coding: utf-8 -*-
2
- from .a2a_protocol_adapter import A2AFastAPIDefaultAdapter
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
+
14
+ # NOTE: NacosRegistry is NOT imported at module import time to avoid forcing
15
+ # an optional dependency on environments that don't have nacos SDK installed.
16
+ # Instead, NacosRegistry is imported lazily via install_lazy_loader when
17
+ # actually needed (e.g., when user does: from ... import NacosRegistry).
18
+
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
+ )