Improve dialog opening and card submitting semantics#222
Open
heyitsaamir wants to merge 14 commits intomainfrom
Open
Improve dialog opening and card submitting semantics#222heyitsaamir wants to merge 14 commits intomainfrom
heyitsaamir wants to merge 14 commits intomainfrom
Conversation
heyitsaamir
added a commit
that referenced
this pull request
Nov 24, 2025
We wrap our AI functions such that we can call the AI plugins etc. But if there are no parameters, we call the function like `foo()`. However, the wrapped function always has 1 positional argument. So python throws. This fixes that bug. #### PR Dependency Tree * **PR #221** 👈 * **PR #222** This tree was auto-generated by [Charcoal](https://github.com/danerwilliams/charcoal)
Contributor
There was a problem hiding this comment.
Pull request overview
This PR introduces cleaner semantics for dialog opening and card action routing by implementing action-based routing patterns. Instead of manually extracting action identifiers from data dictionaries in handler logic, developers can now specify routing identifiers directly in the decorator, improving code clarity and maintainability.
Key Changes
- Introduces
OpenDialogDataandSubmitActionData(aliased fromEnhancedSubmitActionData) utility classes that abstract away the complexity of setting reserved keywords for routing - Enhances
on_dialog_open,on_dialog_submit, andon_card_actiondecorators to accept optional routing identifiers, enabling specific handler matching - Fixes function handler wrapping in
ChatPromptto properly support functions with no parameters when used with plugins
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
packages/cards/src/microsoft/teams/cards/utilities/open_dialog.py |
New utility class for creating dialog open action data with dialog_id routing |
packages/cards/src/microsoft/teams/cards/utilities/submit_dialog.py |
New utility class for creating card submit action data with action routing keyword |
packages/cards/src/microsoft/teams/cards/utilities/__init__.py |
Exports utility classes with convenient naming |
packages/cards/src/microsoft/teams/cards/__init__.py |
Integrates utility classes into main cards package exports |
packages/apps/src/microsoft/teams/apps/routing/activity_handlers.py |
Implements manual handler methods with routing support for dialogs and card actions |
packages/apps/src/microsoft/teams/apps/routing/generated_handlers.py |
Removes auto-generated handlers that are now manually implemented |
packages/apps/src/microsoft/teams/apps/routing/activity_route_configs.py |
Comments out route configs for manually implemented handlers |
packages/apps/tests/test_dialog_routing.py |
Comprehensive tests for dialog routing with and without identifiers |
packages/apps/tests/test_card_action_routing.py |
Comprehensive tests for card action routing with and without identifiers |
packages/ai/src/microsoft/teams/ai/chat_prompt.py |
Fixes function handler wrapping to support parameter-less functions with plugins |
packages/ai/tests/test_chat_prompt.py |
Adds test coverage for parameter-less functions with plugins |
examples/dialogs/src/main.py |
Refactored to use new routing patterns and utility classes |
examples/cards/src/main.py |
Refactored to use action-based routing with separate handlers |
packages/apps/src/microsoft/teams/apps/http_plugin.py |
Minor documentation fix removing unused parameter from example |
packages/cards/src/microsoft/teams/cards/utilities/open_dialog.py
Outdated
Show resolved
Hide resolved
3e81de6 to
5a48dee
Compare
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
lilyydu
reviewed
Nov 26, 2025
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR attempts to improve the devex for dialog opening and card submitting semantics.
For opening a dialog, before:
@app.on_message async def handle_message(ctx: ActivityContext[MessageActivity]) -> None: """Handle message activities and show dialog launcher card.""" # Create the launcher adaptive card using Python objects to demonstrate SubmitActionData # This tests that ms_teams correctly serializes to 'msteams' card = AdaptiveCard(version="1.4") card.body = [TextBlock(text="Select the examples you want to see!", size="Large", weight="Bolder")] - # Use SubmitActionData with ms_teams to test serialization - # SubmitActionData uses extra="allow" to accept custom fields - simple_form_data = SubmitActionData.model_validate({"opendialogtype": "simple_form"}) - simple_form_data.ms_teams = TaskFetchSubmitActionData().model_dump() - card.actions = [ - SubmitAction(title="Simple form test").with_data(simple_form_data) - ] + # Use OpenDialogData to create dialog open actions with clean API + card.actions = [ + SubmitAction(title="Simple form test").with_data(OpenDialogData("simple_form")) + ] # Send the card as an attachment message = MessageActivityInput(text="Enter this form").add_card(card) await ctx.send(message) - @app.on_dialog_open + @app.on_dialog_open("simple_form") async def handle_dialog_open(ctx: ActivityContext[TaskFetchInvokeActivity]): """Handle dialog open events for all dialog types.""" - data: Optional[Any] = ctx.activity.value.data - dialog_type = data.get("opendialogtype") if data else None - if dialog_type == "simple_form": dialog_card = AdaptiveCard.model_validate( { "type": "AdaptiveCard", "version": "1.4", "body": [ {"type": "TextBlock", "text": "This is a simple form", "size": "Large", "weight": "Bolder"}, { "type": "Input.Text", "id": "name", "label": "Name", "placeholder": "Enter your name", "isRequired": True, }, ], "actions": [ {"type": "Action.Submit", "title": "Submit", "data": {"submissiondialogtype": "simple_form"}} ], } ) return InvokeResponse( body=TaskModuleResponse( task=TaskModuleContinueResponse( value=CardTaskModuleTaskInfo( title="Simple Form Dialog", card=card_attachment(AdaptiveCardAttachment(content=dialog_card)), ) ) ) ) - # Default return for unknown dialog types - return TaskModuleResponse(task=TaskModuleMessageResponse(value="Unknown dialog type"))Notice how we have the
opendialogtypethat's kind of hidden away in the code. Then, in youron_dialog_openyou have to pull the action out, and then handle it.The two additions are:
on_dialog_openaccepts a dialog identifier to know when to trigger the handler.Now, with submitting cards, we have a similar issue.
@app.on_dialog_open("simple_form") async def handle_dialog_open(ctx: ActivityContext[TaskFetchInvokeActivity]): """Handle dialog open events for all dialog types.""" dialog_card = AdaptiveCard.model_validate( { "type": "AdaptiveCard", "version": "1.4", "body": [ {"type": "TextBlock", "text": "This is a simple form", "size": "Large", "weight": "Bolder"}, { "type": "Input.Text", "id": "name", "label": "Name", "placeholder": "Enter your name", "isRequired": True, }, ], - "actions": [ - {"type": "Action.Submit", "title": "Submit", "data": {"submissiondialogtype": "simple_form"}} - ], + # Alternative: Use SubmitActionData for cleaner action-based routing + # SubmitAction(title="Submit").with_data(SubmitActionData("submit_simple_form")) + {"type": "Action.Submit", "title": "Submit", "data": {"action": "submit_simple_form"}} } ) return InvokeResponse( body=TaskModuleResponse( task=TaskModuleContinueResponse( value=CardTaskModuleTaskInfo( title="Simple Form Dialog", card=card_attachment(AdaptiveCardAttachment(content=dialog_card)), ) ) ) ) # Default return for unknown dialog types return TaskModuleResponse(task=TaskModuleMessageResponse(value="Unknown dialog type")) - @app.on_dialog_submit + @app.on_dialog_submit("submit_simple_form") async def handle_dialog_submit(ctx: ActivityContext[TaskSubmitInvokeActivity]): """Handle dialog submit events for all dialog types.""" data: Optional[Any] = ctx.activity.value.data - dialog_type = data.get("submissiondialogtype") if data else None - if dialog_type == "simple_form": name = data.get("name") if data else None await ctx.send(f"Hi {name}, thanks for submitting the form!") return TaskModuleResponse(task=TaskModuleMessageResponse(value="Form was submitted")) - return TaskModuleResponse(task=TaskModuleMessageResponse(value="Unknown submission type"))We reserve a keyword "action" to indicate what handler needs to be called when the form is submitted. The keyword
actionwas used to align with web standards.This also extends to general card actions too:
profile_card = AdaptiveCard( schema="http://adaptivecards.io/schemas/adaptive-card.json", body=[ TextBlock(text="User Profile", weight="Bolder", size="Large"), TextInput(id="name").with_label("Name").with_value("John Doe"), TextInput(id="email", label="Email", value="john@contoso.com"), ToggleInput(title="Subscribe to newsletter").with_id("subscribe").with_value("false"), ActionSet( actions=[ ExecuteAction(title="Save") + .with_data(SubmitActionData("save_profile", {"entity_id": "12345"})) .with_associated_inputs("auto"), OpenUrlAction(url="https://adaptivecards.microsoft.com").with_title("Learn More"), ] ), ], ) + @app.on_card_execute_action("save_profile") async def handle_save_profile(ctx: ActivityContext[AdaptiveCardInvokeActivity]) -> AdaptiveCardInvokeResponse: """Handle profile save.""" data = ctx.activity.value.action.data print("Received save_profile action data:", data) entity_id = data.get("entity_id") name = data.get("name", "Unknown") email = data.get("email", "No email") subscribe = data.get("subscribe", "false") age = data.get("age") location = data.get("location", "Not specified") response_text = f"Profile saved!\nName: {name}\nEmail: {email}\nSubscribed: {subscribe}" if entity_id: response_text += f"\nEntity ID: {entity_id}" if age: response_text += f"\nAge: {age}" if location != "Not specified": response_text += f"\nLocation: {location}" await ctx.send(response_text) return AdaptiveCardActionMessageResponse( status_code=200, type="application/vnd.microsoft.activity.message", value="Action processed successfully", )PR Dependency Tree
This tree was auto-generated by Charcoal