Skip to content

Simply HttpPlugin to only focus on server-concepts#249

Merged
heyitsaamir merged 6 commits into
mainfrom
aamirj/simplifyHttp
Mar 13, 2026
Merged

Simply HttpPlugin to only focus on server-concepts#249
heyitsaamir merged 6 commits into
mainfrom
aamirj/simplifyHttp

Conversation

@heyitsaamir

@heyitsaamir heyitsaamir commented Jan 26, 2026

Copy link
Copy Markdown
Collaborator

This is a python adaptation of microsoft/teams.ts#424.

Separate activity sending from HTTP transport layer

The previous architecture tightly coupled HTTP transport concerns with activity sending logic:

Previous Architecture:

HttpPlugin (transport) → implements ISender (sending)
                      → has send() method (creates new Client per call)
                      → has createStream() method
                      → knows about Activity protocol details

ActivityContext → depends on ISender plugin
               → cannot work without transport plugin
               → conflates transport and sending concerns

There are a few issues with this:

  • HttpPlugin created NEW Client instances on every send() call. So there's really no benefit of this logic being in the "httpclient" plugin.
  • Transport plugins (HttpPlugin) were forced to implement send/createStream. This makes it more cumbersome to build your own HttpPlugin with your own servier.
  • Users couldn't "bring their own server" without implementing ISender
  • ActivityContext was tightly coupled to plugin architecture. ("Sender" was coupled with an activity, without any necessary benefits.)

New Architecture

HttpPlugin (transport) → only handles HTTP server/routing/auth
                      → emits CoreActivity (minimal protocol knowledge)
                      → just passes body payload to app

ActivitySender (NEW)  → dedicated class for sending activities
                     → receives injected, reusable Client
                     → handles all send/stream logic
                     → private to App class

ActivityContext       → uses ActivitySender now, which is not a plugin

In this PR, I am mainly decoupling responsibilities of HttpPlugin from being BOTH a listener AND a sender, to being just a listener. The sender bit is now separated to a different ActivitySender class. Other than better code organization, the main thing this lets us do is not require the app to run to be able to send proactive messages. This is a huge plus point because now the App can be used in scenarios where it doesn't necessarily need to listen to incoming messages (like agentic notifications!)

Copilot AI review requested due to automatic review settings January 26, 2026 16:26

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

This pull request simplifies the HttpPlugin by separating activity sending from HTTP transport concerns. The changes adapt the TypeScript PR #424 to Python, introducing a cleaner architectural separation.

Changes:

  • Introduces a new ActivitySender class dedicated to sending activities, removing this responsibility from HttpPlugin
  • Removes the Sender plugin interface and related coupling between transport and sending logic
  • Updates ActivityContext and other components to use ActivitySender instead of depending on plugins
  • Adds app.initialize() method to enable proactive messaging without starting an HTTP server
  • Includes a new proactive-messaging example demonstrating serverless message sending

Reviewed changes

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

Show a summary per file
File Description
uv.lock Adds proactive-messaging example package to workspace
packages/devtools/src/microsoft_teams/devtools/devtools_plugin.py Updates to use PluginBase instead of Sender, converts Activity to CoreActivity
packages/botbuilder/src/microsoft_teams/botbuilder/botbuilder_plugin.py Updates signature to accept CoreActivity parameter, calls parent with new signature
packages/apps/src/microsoft_teams/apps/routing/activity_context.py Replaces Sender dependency with ActivitySender
packages/apps/src/microsoft_teams/apps/plugins/sender.py Removes the Sender abstract class entirely
packages/apps/src/microsoft_teams/apps/plugins/plugin_error_event.py Removes sender field from error events
packages/apps/src/microsoft_teams/apps/plugins/plugin_base.py Removes send() and create_stream() methods from base plugin
packages/apps/src/microsoft_teams/apps/plugins/plugin_activity_sent_event.py Removes sender field from activity sent events
packages/apps/src/microsoft_teams/apps/plugins/plugin_activity_response_event.py Removes sender field from activity response events
packages/apps/src/microsoft_teams/apps/plugins/plugin_activity_event.py Removes sender field from plugin activity events
packages/apps/src/microsoft_teams/apps/plugins/init.py Removes Sender from exports
packages/apps/src/microsoft_teams/apps/http_plugin.py Removes sending logic, updates to work with CoreActivity, changes to PluginBase
packages/apps/src/microsoft_teams/apps/events/types.py Adds CoreActivity model, removes sender from event dataclasses
packages/apps/src/microsoft_teams/apps/events/init.py Exports CoreActivity
packages/apps/src/microsoft_teams/apps/contexts/function_context.py Uses ActivitySender instead of HttpPlugin for sending
packages/apps/src/microsoft_teams/apps/app_process.py Updates to work without Sender, converts CoreActivity back to Activity
packages/apps/src/microsoft_teams/apps/app_plugins.py Removes Sender dependency from plugin injection
packages/apps/src/microsoft_teams/apps/app_events.py Updates event management to work without sender parameter
packages/apps/src/microsoft_teams/apps/app.py Adds ActivitySender, implements initialize() method, updates send() to use ActivitySender
packages/apps/src/microsoft_teams/apps/activity_sender.py New class handling activity sending and streaming
examples/proactive-messaging/src/main.py Example demonstrating proactive messaging without server
examples/proactive-messaging/pyproject.toml Package configuration for proactive messaging example
examples/proactive-messaging/README.md Documentation for proactive messaging example
Comments suppressed due to low confidence (1)

packages/apps/src/microsoft_teams/apps/http_plugin.py:317

  • The route handler signature has changed from parsing the request body internally to expecting core_activity: CoreActivity as a parameter. While FastAPI can automatically parse Pydantic models from request bodies, this pattern differs from the manual parsing approach used elsewhere in the codebase (e.g., in the func decorator where await r.json() is used).

Please verify that FastAPI correctly injects the CoreActivity parameter when the route is called. If this doesn't work as expected at runtime, consider either:

  1. Manually parsing the body: body = await request.json(); core_activity = CoreActivity.model_validate(body)
  2. Using FastAPI's Body annotation: core_activity: CoreActivity = Body(...)

Note that existing tests likely need updates to match this new signature.

    async def on_activity_request(self, core_activity: CoreActivity, request: Request, response: Response) -> Any:
        """Handle incoming Teams activity."""
        # Get validated token from middleware (if present - will be missing if skip_auth is True)
        if hasattr(request.state, "validated_token") and request.state.validated_token:
            token = request.state.validated_token
        else:
            token = cast(
                TokenProtocol,
                SimpleNamespace(
                    app_id="",
                    app_display_name="",
                    tenant_id="",
                    service_url=core_activity.service_url or "",
                    from_="azure",
                    from_id="",
                    is_expired=lambda: False,
                ),
            )

        activity_type = core_activity.type or "unknown"
        activity_id = core_activity.id or "unknown"

        self.logger.debug(f"Received activity: {activity_type} (ID: {activity_id})")
        self.logger.debug(f"Processing activity {activity_id} via handler...")

        # Process the activity
        result = await self._process_activity(core_activity, token)
        return self._handle_activity_response(response, result)

    def _setup_routes(self) -> None:
        """Setup FastAPI routes."""

        self.app.post("/api/messages")(self.on_activity_request)

Comment thread packages/apps/src/microsoft_teams/apps/plugins/__init__.py
Comment thread packages/apps/tests/test_http_plugin.py Outdated
Comment thread examples/proactive-messaging/pyproject.toml
Comment thread examples/proactive-messaging/pyproject.toml Outdated
Comment thread packages/apps/src/microsoft_teams/apps/plugins/__init__.py

@lilyydu lilyydu left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nice enhancements! Agree with most of the changes, just left a few comments about the sender, - also did a dive with Claude:

Image
Image
Image

Comment thread packages/apps/src/microsoft_teams/apps/activity_sender.py Outdated
Comment thread packages/apps/src/microsoft_teams/apps/app.py
Comment thread packages/apps/src/microsoft_teams/apps/app.py Outdated
@heyitsaamir

heyitsaamir commented Mar 12, 2026

Copy link
Copy Markdown
Collaborator Author

@lilyydu - great points

  1. CoreActivity is so simple, that I think it should be okay. FastAPI will log errors too so that should help us debug.
  2. I think this is fine. This was the behavior before too. This is also kind of dependent if you do app.on('error') before doing app.initialize(). Not much we can control here I think?
  3. Ehh, adding locks is risky, and would add to more issues. This is pretty low probability imo.

Wdyt?

@heyitsaamir
heyitsaamir force-pushed the aamirj/simplifyHttp branch from 8dc943a to 2c91aa4 Compare March 12, 2026 19:45
@heyitsaamir

heyitsaamir commented Mar 12, 2026

Copy link
Copy Markdown
Collaborator Author

This change is part of the following stack:

Change managed by git-spice.

@heyitsaamir
heyitsaamir merged commit c96f434 into main Mar 13, 2026
7 checks passed
@heyitsaamir
heyitsaamir deleted the aamirj/simplifyHttp branch March 13, 2026 06:02
heyitsaamir added a commit that referenced this pull request Mar 13, 2026
## Summary
- **Replace `HttpPlugin` with `HttpServer` + `HttpServerAdapter`
pattern** — follows the [TS PR
#433](AzureAD/microsoft-authentication-library-for-js#433)
approach
- `HttpServer` is no longer a plugin — it's owned directly by `App`
- Introduces `HttpServerAdapter` protocol with `FastAPIAdapter` as the
default implementation
- Plugins inject `HttpServer` via DI and access the framework via
`server.adapter`
- Framework-agnostic handler pattern: `async (HttpRequest) ->
HttpResponse`
- Rewrites `remote_function_jwt_middleware` to be framework-agnostic
- Adds `examples/http-adapters/` with Starlette adapter and non-managed
FastAPI examples

Stacked on #249

## Test plan
- [x] Update unit tests
- [x] Run `poe test` — all tests pass
- [x] Run AI sample end-to-end
- [x] Run MCP sample end-to-end
- [x] Run Echo Bot sample end-to-end
- [x] Run Dialog sample end-to-end
- [x] Run Tab sample end-to-end
- [x] Run BotBuilder sample end-to-end

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.6 <[email protected]>
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.

3 participants