Skip to content

AgentApplication.AdaptiveCard support - #518

Merged
Rodrigo Brandão (rodrigobr-msft) merged 11 commits into
mainfrom
users/robrandao/adaptive-cards
Aug 7, 2026
Merged

AgentApplication.AdaptiveCard support#518
Rodrigo Brandão (rodrigobr-msft) merged 11 commits into
mainfrom
users/robrandao/adaptive-cards

Conversation

@rodrigobr-msft

@rodrigobr-msft Rodrigo Brandão (rodrigobr-msft) commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

This pull request introduces comprehensive support for Adaptive Card activities, especially focusing on dynamic search and invoke actions, by adding new models, handler interfaces, and registration mechanisms. It also extends content type support and refines several existing data models for better type safety and flexibility.

Key highlights:

  • Adds new models and handler protocols to support Adaptive Card search and invoke actions.
  • Implements a registration and routing system for Adaptive Card handlers.
  • Expands content type constants for richer response handling.
  • Refines existing models for improved typing and default behaviors.

Most important changes:

Adaptive Card Search and Invoke Support

  • Added new models: SearchInvokeOptions, SearchInvokeValue, and AdaptiveCardSearchInvokeValue to represent search-related invoke payloads, and registered them in __init__.py for public API exposure. [1] [2] [3] [4] [5] [6] [7]
  • Introduced handler protocols (ActionExecuteHandler, ActionSubmitHandler, SearchHandler) and data models (AdaptiveCardSearchParams, AdaptiveCardSearchResult, Query) for Adaptive Card activity handling in the hosting core. [1] [2]
  • Implemented the AdaptiveCard class with registration methods (action_execute, action_submit, search) for routing Adaptive Card actions and search requests, including validation and response composition.
  • Added a factory.py module with helper functions to generate standard AdaptiveCardInvokeResponse objects for various scenarios (success, error, login, etc.).

Content Types and Model Improvements

  • Extended ContentTypes with new constants for error, message, login request, and search response types, enabling richer and more standardized responses.
  • Updated AdaptiveCardInvokeAction, AdaptiveCardInvokeValue, and AdaptiveCardInvokeResponse models for improved type annotations, optional fields, and better default handling using Pydantic's Field. [1] [2] [3] [4]

These changes collectively enable robust, extensible handling of Adaptive Card actions and dynamic search scenarios in the agent hosting core.

Copilot AI lite review requested due to automatic review settings August 3, 2026 22:18
@rodrigobr-msft Rodrigo Brandão (rodrigobr-msft) changed the title First commit for AdaptiveCard support in AgentApplication AgentApplication.AdaptiveCard support Aug 3, 2026
@rodrigobr-msft Rodrigo Brandão (rodrigobr-msft) linked an issue Aug 3, 2026 that may be closed by this pull request

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new Adaptive Card routing/response surface under microsoft_agents.hosting.core.app intended to support Action.Execute, Action.Submit, and Adaptive Card dynamic search invoke flows, plus supporting response/content-type updates in the activity models.

Changes:

  • Introduces an AdaptiveCard route registrar (execute/submit/search) and related handler type defs/models.
  • Adds an Adaptive Card invoke-response factory module for common response shapes and error handling.
  • Expands AdaptiveCardInvokeResponse.value to accept non-dict payloads and adds additional ContentTypes constants for invoke responses/errors.

Reviewed changes

Copilot reviewed 8 out of 9 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/adaptive_card/utils.py Adds (currently commented-out) utility placeholder code related to search invoke validation.
libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/adaptive_card/models.py Adds dataclass models for search query params/results.
libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/adaptive_card/factory.py Adds helpers to construct AdaptiveCardInvokeResponse objects (adaptive card, search, errors, auth).
libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/adaptive_card/adaptive_card.py Adds the Adaptive Card routing registrar (Action.Execute/Submit/Search) and invoke-response sending logic.
libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/adaptive_card/adaptive_card_options.py Adds options type intended to configure Adaptive Card behaviors (e.g., submit filter).
libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/adaptive_card/_type_defs.py Adds protocol handler signatures for adaptive card routes.
libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/adaptive_card/init.py Package initializer (currently empty).
libraries/microsoft-agents-activity/microsoft_agents/activity/content_types.py Adds content-type constants for invoke error/message/login/search responses.
libraries/microsoft-agents-activity/microsoft_agents/activity/adaptive_card_invoke_response.py Broadens value to object to support more response payload shapes.
Suppressed comments (2)

libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/adaptive_card/factory.py:91

  • not_supported() currently passes the detailed message as the error "code" and the literal "NotSupported" as the human message, which flips code/message in the payload.
def not_supported(message: str) -> AdaptiveCardInvokeResponse:
    return error(
        HTTPStatus.NOT_IMPLEMENTED,
        "NotSupported",
        message,
    )

libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/adaptive_card/factory.py:99

  • internal_error() currently passes the detailed message as the error "code" and the literal "InternalError" as the human message, which flips code/message in the payload.
def internal_error(message: str) -> AdaptiveCardInvokeResponse:
    return error(
        HTTPStatus.INTERNAL_SERVER_ERROR,
        "InternalError",
        message,
    )

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copilot AI review requested due to automatic review settings August 5, 2026 21:14

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 13 changed files in this pull request and generated 2 comments.

Suppressed comments (9)

libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/adaptive_card/factory.py:83

  • bad_request() is passing arguments to error() in the wrong order, which swaps the intended error "code" and "message" fields in the response body.
def bad_request(message: str) -> AdaptiveCardInvokeResponse:
    return error(
        HTTPStatus.BAD_REQUEST,
        "BadRequest",
        message,
    )

libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/adaptive_card/factory.py:91

  • not_supported() is passing arguments to error() in the wrong order, which swaps the intended error "code" and "message" fields in the response body.
def not_supported(message: str) -> AdaptiveCardInvokeResponse:
    return error(
        HTTPStatus.NOT_IMPLEMENTED,
        "NotSupported",
        message,
    )

libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/adaptive_card/factory.py:99

  • internal_error() is passing arguments to error() in the wrong order, which swaps the intended error "code" and "message" fields in the response body.
def internal_error(message: str) -> AdaptiveCardInvokeResponse:
    return error(
        HTTPStatus.INTERNAL_SERVER_ERROR,
        "InternalError",
        message,
    )

libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/adaptive_card/adaptive_card.py:116

  • Activity.value is typically a dict when parsed from incoming JSON, so getattr(activity.value, submit_filter, None) will not find the submit field and the route selector will never match. Handle dict values explicitly.
            verb_value = None
            if activity.value is not None:
                verb_value = getattr(activity.value, submit_filter, None)
            return self._matches(verb, verb_value)

libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/adaptive_card/adaptive_card.py:154

  • Activity.value is typically a dict when parsed from incoming JSON, so getattr(activity.value, "dataset", None) will not read the dataset and the search selector will never match. Handle dict values explicitly.
            dataset_value = (
                getattr(activity.value, "dataset", None)
                if activity.value is not None
                else None
            )

libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/adaptive_card/utils.py:5

  • This module is currently entirely commented-out code. Keeping large blocks of commented code makes maintenance harder and tends to drift out of date; either remove the file from the PR or replace it with working utilities (and corresponding tests).
# # Copyright (c) Microsoft Corporation. All rights reserved.
# # Licensed under the MIT License.

# import pydantic

libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/adaptive_card/adaptive_card.py:55

  • New AdaptiveCard routing/validation behavior is introduced here, but there are existing route/selector tests for AgentApplication (tests/hosting_core/app/test_agent_application*.py) and no corresponding tests for AdaptiveCard (e.g., selector matching when Activity.value is a dict, and correct invoke_response payloads). Adding tests would help prevent regressions.
    def action_execute(
        self,
        verb: str | Pattern[str],
        *,
        auth_handlers: list[str] | None = None,
        **kwargs,
    ) -> Callable[[ActionExecuteHandler], ActionExecuteHandler]:
        """Register an ``Action.Execute`` handler that receives the action data."""

libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/adaptive_card/_type_defs.py:4

  • TypeVar is imported but never used in this module; this will fail linting in typical configurations. Remove the unused import.
from typing import TypeVar, Awaitable, Protocol

libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/agent_application.py:25

  • The PR title suggests AgentApplication.AdaptiveCard support, but AgentApplication currently has no AdaptiveCard registrar/property/method (and no imports or references). As-is, consumers must manually import and instantiate AdaptiveCard, which doesn't match the advertised API surface.
    TypeVar,
    cast,
    overload,
    Optional,
)

Copilot AI review requested due to automatic review settings August 5, 2026 22:03

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 15 out of 15 changed files in this pull request and generated no new comments.

Suppressed comments (4)

libraries/microsoft-agents-activity/microsoft_agents/activity/search_invoke_value.py:21

  • SearchInvokeValue.kind and query_text are required, but the hosting-core Adaptive Card search validator contains logic to treat missing/empty kind (Teams fallback) and missing queryText as recoverable inputs with a specific error message. With required fields, a missing property will raise validation errors before that logic runs, so the intended behavior can’t be reached.
    kind: str
    query_text: str
    query_options: SearchInvokeOptions

libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/adaptive_card/adaptive_card.py:88

  • action_execute always sends an InvokeResponse with HTTP status 200 (status_code=HTTPStatus.OK), even when _validate_action_execute_value returns an error/not-supported response. Also response = response or ... won’t set a default status_code if the handler returns an AdaptiveCardInvokeResponse with status_code=None, which can lead to missing/incorrect status propagation.
                await self._send_invoke_response(
                    context, response, status_code=HTTPStatus.OK
                )

libraries/microsoft-agents-activity/microsoft_agents/activity/adaptive_card_invoke_value.py:24

  • AdaptiveCardInvokeValue now requires both action and authentication, but existing tests/handlers expect to be able to validate payloads that omit authentication (and even omit parts of action) and then return a specific adaptive-card error response. Requiring these fields causes model_validate to fail early and changes behavior for real incoming activities.
    action: AdaptiveCardInvokeAction
    authentication: TokenExchangeInvokeRequest

libraries/microsoft-agents-activity/microsoft_agents/activity/adaptive_card_invoke_action.py:29

  • Making id and verb required breaks existing usage/tests that construct an Adaptive Card invoke payload with only {"action": {"type": "Action.Execute"}} (see tests/hosting_core/test_activity_handler.py:142). These fields should remain optional to preserve backward compatibility and allow partial payloads during validation/error handling.
    type: str
    id: str
    verb: str
    data: dict[NonEmptyString, object] = Field(default_factory=dict)

Copilot AI review requested due to automatic review settings August 5, 2026 22:31

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 25 out of 25 changed files in this pull request and generated no new comments.

Suppressed comments (2)

libraries/microsoft-agents-activity/microsoft_agents/activity/search_invoke_value.py:21

  • AdaptiveCard._validate_search_value() attempts to default kind to "search" for Teams when it is missing/empty, but SearchInvokeValue.kind is currently required. If kind is omitted, Pydantic validation fails earlier and this fallback never runs.
    kind: str
    query_text: str
    query_options: SearchInvokeOptions

libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/app/adaptive_card/adaptive_card.py:88

  • Action.Execute always sends an InvokeResponse with HTTP status 200, even when _validate_action_execute_value() returns a 4xx/5xx AdaptiveCardInvokeResponse (e.g., from factory.bad_request). Channels typically rely on the outer InvokeResponse status for error handling, so this can mask failures.
                await self._send_invoke_response(
                    context, response, status_code=HTTPStatus.OK
                )

@rodrigobr-msft
Rodrigo Brandão (rodrigobr-msft) marked this pull request as ready for review August 5, 2026 22:40
Copilot AI review requested due to automatic review settings August 5, 2026 22:40

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 25 out of 25 changed files in this pull request and generated 1 comment.

Suppressed comments (1)

libraries/microsoft-agents-activity/microsoft_agents/activity/adaptive_card_invoke_value.py:24

  • AdaptiveCardInvokeValue.action was changed to be required, but existing hosting-core code paths still rely on it being optional (e.g., activity_handler.py checks if invoke_value.action is None: to produce a specific "Missing action property" error). With the current type, missing/null action will raise validation errors earlier and those branches become unreachable, changing externally visible error behavior.
    action: AdaptiveCardInvokeAction
    authentication: TokenExchangeInvokeRequest | None = None

@kylerohn-msft Kyle Rohn (kylerohn-msft) left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changelog and description?

Copilot AI review requested due to automatic review settings August 6, 2026 19:45

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copilot AI review requested due to automatic review settings August 7, 2026 19:01

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 26 out of 26 changed files in this pull request and generated no new comments.

Suppressed comments (2)

libraries/microsoft-agents-activity/microsoft_agents/activity/search_invoke_value.py:21

  • SearchInvokeValue declares kind and query_text as required fields, but AdaptiveCard._validate_search_value() explicitly tries to handle missing/empty values (and even auto-fills kind for Teams). With the current required typing, a missing kind/queryText will raise a Pydantic ValidationError and return the generic "not properly formed" bad_request instead of the intended targeted missing-field handling (and the Teams auto-fill path will never execute).
    kind: str
    query_text: str
    query_options: SearchInvokeOptions

libraries/microsoft-agents-activity/microsoft_agents/activity/adaptive_card_invoke_value.py:24

  • AdaptiveCardInvokeValue.action is now required, but both ActivityHandler._get_adaptive_card_invoke_value() and AdaptiveCard._validate_action_execute_value() still contain explicit if invoke_value.action is None / "Missing action property" logic. With action required, payloads missing action will fail validation earlier and return the more generic "Value property is not properly formed" instead of the intended missing-action error handling.
    action: AdaptiveCardInvokeAction
    authentication: TokenExchangeInvokeRequest | None = None

Copilot AI review requested due to automatic review settings August 7, 2026 20:09
@rodrigobr-msft
Rodrigo Brandão (rodrigobr-msft) merged commit bac57ab into main Aug 7, 2026
11 checks passed
@rodrigobr-msft
Rodrigo Brandão (rodrigobr-msft) deleted the users/robrandao/adaptive-cards branch August 7, 2026 20:14

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 26 out of 26 changed files in this pull request and generated no new comments.

Suppressed comments (3)

libraries/microsoft-agents-activity/microsoft_agents/activity/search_invoke_value.py:21

  • AdaptiveCard._validate_search_value() tries to accept missing kind (setting it to "search" for Teams) and to produce a targeted "Missing 'kind'" error, but SearchInvokeValue currently requires kind and query_text. That means Pydantic validation fails before this logic runs, so the intended fallback / error messaging is unreachable.
    kind: str
    query_text: str
    query_options: SearchInvokeOptions

libraries/microsoft-agents-activity/microsoft_agents/activity/adaptive_card_invoke_action.py:29

  • AdaptiveCardInvokeAction dropped the NonEmptyString constraint for type (and also for id/verb). This weakens validation and allows empty strings for fields that are used for routing/behavior, which can lead to hard-to-diagnose failures. Keeping the non-empty constraint preserves the existing type-safety guarantees while still allowing id/verb to be optional.
    type: str
    id: str | None = None
    verb: str | None = None
    data: dict[NonEmptyString, object] = Field(default_factory=dict)

tests/hosting_core/app/test_adaptive_card.py:288

  • This test hardcodes the search response content-type string even though the PR adds ContentTypes.search_response. Using the constant keeps the test aligned with the public API and avoids brittle string literals.
    assert response.value.body == {
        "statusCode": 200,
        "type": "application/vnd.microsoft.search.searchResponse",
        "value": {"results": [{"title": "Title", "value": "Value"}]},
    }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

dynamic adaptive card

3 participants