class DRAgentA2AConfig(BaseModel):
"""DR-owned wrapper around NAT's A2AFrontEndConfig with optional skill definitions."""
server: A2AFrontEndConfig = Field(description="NAT A2A server configuration.")
cross_application_access: CrossApplicationAccessConfig | None = Field(
default=None,
description=(
"Configuration for Cross-Application Access utilizing a hybrid RFC 8693 / "
"RFC 7523 flow."
),
)
skills: list[AgentSkill] = Field(
default=[],
description="Skills to advertise in the A2A agent card. "
"If empty, a single default skill is generated from the agent name and description.",
)
external: DRAgentA2AExternalConfig | None = Field(
default=None,
description="External identity and URL override for the agent card.",
)
oauth_claim_validation: bool = Field(
default=False,
description=(
"Opt in to L2 validation of the inbound IdP token's claims. Off unless set to "
"true, so an agent never starts enforcing because ``cross_application_access`` "
"was filled in for the agent card. Today this governs the ``aud`` claim, taken "
"from ``cross_application_access.token_request.audience``; ``scope`` joins it "
"under the same flag. Applies to every route, not only /a2a, because an inbound "
"token reaches the workflow the same way whichever route it arrives on."
),
)
enable_unauthenticated_well_known_route: bool = Field(
default=False,
description=(
"Per-agent developer opt-in for unauthenticated "
"GET /.well-known/agent-card.json. Also requires platform-level "
"opt-in per cluster to route unauthenticated traffic to the agent. "
"When disabled (default), unauthenticated requests receive the same "
"generic 404 as a nonexistent agent, so the refusal does not reveal "
"that the agent exists. When enabled, anonymous callers receive a "
"redacted agent card."
),
)
redacted_agent_card: DRAgentA2ARedactedAgentCardConfig = Field(
default_factory=DRAgentA2ARedactedAgentCardConfig,
description="Options shaping the card served to unauthenticated callers.",
)
mount_path: str = Field(
default=A2A_MOUNT_PATH,
# Pydantic skips validators for defaults unless asked; without this the default
# would be the one value never normalized or checked.
validate_default=True,
description=(
"Path suffix the A2A server is mounted under, relative to the app root. "
'Defaults to "a2a"; set to a different value, e.g. "agent" or "api/a2a", to '
'mount it elsewhere. Leading and trailing slashes are stripped, so "/a2a/" '
'and "a2a" are equivalent. The advertised agent card URL follows this value '
"automatically, and the agent card is additionally served at the root "
".well-known/agent-card.json as a discovery fallback. "
"Each slash-separated segment must be made of RFC 3986 unreserved characters "
"(letters, digits, and - . _ ~) and must not begin with a dot. Mounting at "
"the application root is not supported: an empty value is rejected. Mounting "
"on a path already served by another route is rejected at startup."
),
)
@field_validator("mount_path")
@classmethod
def _normalize_mount_path(cls, value: str) -> str:
"""Normalize ``mount_path`` and reject values that cannot safely be a path suffix.
This value is interpolated raw into two different places — a Starlette route
pattern (``app.mount(f"/{mount_path}", ...)``) and the ``url`` advertised in the
agent card — so it has to be valid as both. Nothing downstream re-checks it:
``a2a.types.AgentCard.url`` is an unvalidated ``str`` despite documenting that it
must be a valid absolute URL, so this validator is the only enforcement point.
Surrounding slashes are stripped, so ``"a2a"`` and ``"/a2a/"`` agree; interior
slashes are kept for multi-segment mounts such as ``"api/a2a"``.
Four rejections, each covering a failure that is otherwise silent:
* **Empty** (``""``, ``"/"``): would mount A2A at the application root, where its
catch-all ``Mount`` shadows any route registered after it, with no error.
* **Characters outside RFC 3986 §2.3 ``unreserved``**: ``{``/``}`` are Starlette
path-parameter syntax — ``"{id}"`` compiles to a wildcard that swallows unrelated
paths, and ``"{path}"`` collides with ``Mount``'s own parameter and crashes at
startup. ``?``, ``#``, ``%`` and whitespace are URI syntax or need encoding, so
the advertised URL resolves somewhere other than the mount and A2A becomes
silently undiscoverable.
* **Empty interior segments** (``"a2a//nested"``): advertises a URL clients and
proxies normalize differently than the route matches.
* **Dot-leading segments** (``"."``, ``".."``, ``".well-known"``): ``.``/``..`` are
relative-reference syntax that clients resolve away, and RFC 8615 reserves
``/.well-known/`` as a registry-controlled namespace which the A2A protocol
itself uses for agent card discovery.
"""
normalized = value.strip().strip("/")
if not normalized:
_reject_mount_path(
"must not be empty; mounting A2A at the application root is not supported",
repr(value),
)
if len(normalized) > _MOUNT_PATH_MAX_LENGTH:
# Length only: echoing an over-long value back would bury the message in it.
_reject_mount_path(
f"must be at most {_MOUNT_PATH_MAX_LENGTH} characters",
f"{len(normalized)}",
)
for segment in normalized.split("/"):
if not segment:
_reject_mount_path(
"must not contain an empty path segment; use a single slash between segments",
repr(value),
)
if segment.startswith("."):
_reject_mount_path(
"segments must not start with a dot: dot segments are "
"relative-reference syntax that clients resolve away, and "
"/.well-known/ is reserved by RFC 8615 for discovery",
repr(value),
)
if not _MOUNT_PATH_SEGMENT_RE.fullmatch(segment):
_reject_mount_path(
"segments must be made of letters, digits, and - . _ ~ (RFC 3986 "
"unreserved characters): the value is used both as the mount point "
"and in the agent card URL, so anything needing percent-encoding or "
"carrying URI syntax would make one of the two wrong",
# Names the offending segment, which a multi-segment value would
# otherwise leave the reader to spot.
repr(segment) if segment == normalized else f"{segment!r} in {value!r}",
)
return normalized