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