Skip to content

datarobot_genai.dragent.frontends.register

register

DRAgentA2AExternalConfig

Bases: BaseModel

Customer-provided external identity and URL override for the agent card.

Source code in datarobot_genai/dragent/frontends/register.py
class DRAgentA2AExternalConfig(BaseModel):
    """Customer-provided external identity and URL override for the agent card."""

    id: str | None = Field(
        default=None, description="External agent identifier for catalog discovery."
    )
    url: str | None = Field(
        default=None, description="Custom external URL override for the agent card endpoint."
    )

DRAgentA2ARedactedAgentCardConfig

Bases: BaseModel

Placeholder group for options shaping the card served to unauthenticated callers.

Unauthenticated GET /.well-known/agent-card.json requests never see the full card built by create_agent_card() -- they see the redacted card produced by redact_agent_card(). New options that change what that redacted view contains belong here, so they stay discoverable in one place instead of accreting as unrelated top-level a2a.* fields.

Source code in datarobot_genai/dragent/frontends/register.py
class DRAgentA2ARedactedAgentCardConfig(BaseModel):
    """Placeholder group for options shaping the card served to unauthenticated callers.

    Unauthenticated ``GET /.well-known/agent-card.json`` requests never see the full
    card built by ``create_agent_card()`` -- they see the *redacted* card produced by
    ``redact_agent_card()``. New options that change what that redacted view contains
    belong here, so they stay discoverable in one place instead of accreting as
    unrelated top-level ``a2a.*`` fields.
    """

    enable_skills: bool = Field(
        default=False,
        description=(
            "Per-agent developer opt-in to advertise skills on the redacted agent "
            "card served to unauthenticated callers (has no effect unless "
            "``a2a.enable_unauthenticated_well_known_route`` is also enabled). Off "
            "by default, so anonymous callers see no skills unless an agent "
            "developer explicitly opts in. When enabled, the redacted card's "
            "``skills`` list matches the authenticated card's."
        ),
    )

DRAgentA2AConfig

Bases: BaseModel

DR-owned wrapper around NAT's A2AFrontEndConfig with optional skill definitions.

Source code in datarobot_genai/dragent/frontends/register.py
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