Skip to content

Move internal workspace packages from peer deps to direct deps#474

Merged
heyitsaamir merged 8 commits intomainfrom
aamirj/direct-deps
Mar 23, 2026
Merged

Move internal workspace packages from peer deps to direct deps#474
heyitsaamir merged 8 commits intomainfrom
aamirj/direct-deps

Conversation

@heyitsaamir
Copy link
Copy Markdown
Collaborator

@heyitsaamir heyitsaamir commented Mar 13, 2026

Why

Internal @microsoft/teams.* packages were declared as peer dependencies of each other, forcing consumers to manually install packages like teams.api, teams.common, teams.graph, and teams.cards even though they had no version choice — all packages are lockstep-versioned and released together. This added DX friction without any architectural benefit.

This was fine when agents were being created from scratch (users would scaffold a project and install everything at once). That's no longer the case — users are integrating the SDK into existing projects, and having to figure out which internal plumbing packages to install is unnecessary. Peer deps are meant for plugin/host relationships and external SDKs where the consumer provides their own version, not for tightly-coupled internal packages.

Summary

  • Converted internal @microsoft/teams.* peer dependencies to real dependencies across all packages. Consumers now only install the packages they consciously choose (teams.apps, teams.dev, teams.ai, etc.) and internal plumbing comes in transitively.
  • Plugin packages (botbuilder, dev, mcp, a2a, mcpclient) correctly peer on their host (teams.apps or teams.ai) while keeping directly-imported internals as real deps. External third-party peers (botbuilder, openai, @microsoft/teams-js, @modelcontextprotocol/sdk, @a2a-js/sdk) remain as peer deps.
  • Cleaned up examples and CLI templates to only declare dependencies they actually import — removed ~16 redundant deps across examples and ~8 across CLI templates.
  • Removed @microsoft/teams.dev dependency from teams.mcp (and associated DevtoolsPlugin code) - This requires a dependency on dev, which is not right. Users should be able to run this plugin without installing dev since dev is an optional plugin.

Dependency principles applied

  1. dependencies = anything your code directly imports
  2. peerDependencies = the host/platform your package is a plugin for, plus external SDKs the consumer provides
  3. Don't declare what you don't import; don't omit what you do

Test plan

  • Full monorepo build passes (33/33 tasks)
  • Verify examples still run locally
  • Verify CLI teams new scaffolds correct deps

skip-test-verification

heyitsaamir and others added 4 commits March 13, 2026 15:31
Internal @microsoft/teams.* packages were declared as peer dependencies,
forcing consumers to manually install internal plumbing. Convert to real
dependencies so consumers only install packages they consciously choose.

Clean up examples and CLI templates to only declare what they import.
Remove teams.dev dependency from teams.mcp (and DevtoolsPlugin code).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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 ICoreActivity (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!)

## Major Decisions

### 1. Created ActivitySender Class
- Centralized all activity sending logic
- Receives reusable Client in constructor (no per-send instantiation)
- Private to App class - internal implementation detail
- Provides send() and createStream() methods
- **Separate from HttpPlugin**

### 2. Introduced ICoreActivity Interface
- Minimal fields transport layer needs: serviceUrl, id, type
- Extensible via [key: string]: any for protocol-specific fields
- Transport plugins work with this instead of full Activity type. So
it's easier to create these.
- Parsing to Activity happens in app.process.ts now, NOT in HttpPlugin.

### 3. Removed ISender Interface
- No longer needed - plugins don't send activities
- Plugins only handle transport (receiving requests)
- Breaking change, but simplifies plugin architecture. This pattern
wasn't documented (intentionally) because the design was subject to
change. So it should be okay hopefully to change this.

## Breaking Changes

### For Plugin Authors:
1. **ISender removed** - Custom plugins should implement IPlugin only
2. **IActivityEvent changed** - Now has body: ICoreActivity instead of
activity: Activity











#### PR Dependency Tree


* **PR #424** 👈
  * **PR #433**

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

---------

Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
## Summary
- Run `npm audit fix` to resolve 13 vulnerabilities in transitive
dependencies (hono, @hono/node-server, express-rate-limit, flatted,
js-yaml, minimatch, tmp)
- Change `botbuilder` from 4.23.1 to ^4.23.1 in `packages/botbuilder`
and change to 4.23.3 in `examples/botbuilder` to resolve the elliptic
vulnerability chain

## Test plan
- [x] Tested `examples/mcp`
- [x] Tested `examples/mcpclient`
- [x] Tested `examples/botbuilder`
- [x] `npm audit` returns 0 vulnerabilities

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
In this PR, we introduce a new object called HttpServer and begin to
deprecate HttpPlugin.

## Main changes
1. Create `HttpServer` internal class. It accepts an
`IHttpServerAdapter` which is the server implementation.
2. Pulled out the express implementation as an `IHttpServerAdapter`
3. Deprecated HttpPlugin, and made it use HttpServer + ExpressAdapter.
4. Changed BotBuilder/A2A/Mcp plugins to depend on HttpServer vs.
HttpPlugin.
5. Added examples to show how powerful IHttpServerAdapter can be with
different types of servers (hono, fastify).
6. Minor refactor of the jwt middleware such that we can reuse it in
HttpServer and app.embed.

## Why:
HTTP is a core part of our sdk. Our App object uses HTTP to set up a
server, perform auth validations, and pipe the request to the handlers
that are attached, and then return the response. Key part is that Http
is a *core* part of App, not a plugin, since core functionality is
dependent on it.
Even inside the App object, we were doing special casing for this
Http"Plugin" whereas it should never have really been a plugin to begin
with. By making it a plugin, we were exposing many non-plugin essential
things to the plugin system in general.

So what should it have been? Well, HTTP Plugin had these
responsibilities
1. Set up the express server
2. Perform validations if credentials were present
3. Pass the incoming request to App
4. Once App handlers have had a chance to process this incoming request,
pass the response back to the server.

So, we introduce a new object called `HttpServer` whose responsibilities
are essentially that ^. This object is not a plugin, but an object
that's created by App itself.

## Customization

Now this idealogical shift doesn't really warrant us doing this
refactor, but we started seeing requests from folks who wanted to hook
Teams functionality into existing servers, or replace the underlying
server infra with a non-express server. Our recommendation was to
rebuild a new HttpPlugin. But rebuilding this plugin is not simple
(since we don't really document it anywhere, and didn't expect folks to
build their own).
So `HttpServer` exposes an `IHttpServerAdapter` concept. To build the
adapter, one simply needs to build out a handler for extracting request
data, and a handler for responses. This means that you can build simple
custom adapters for your own _existing_ servers. (And if you don't pass
one in, we'll build a default express one.) Examples of servers are in
the http-adapters folder under examples/.

## Adapter Interface

The `IHttpServerAdapter` interface adapters need to implement:

```typescript
interface IHttpServerAdapter {
  registerRoute(method: HttpMethod, path: string, handler: HttpRouteHandler): void;
  serveStatic?(path: string, directory: string): void;
  start?(port: number): Promise<void>;
  stop?(): Promise<void>;
}
```

Handlers are pure functions — `({ body, headers }) → { status, body }`.
No framework-specific request/response objects leak through the
abstraction.

### Why `registerRoute`?

Some adapter patterns have the adapter own routing internally and just
receive a single callback. But our SDK creates routes dynamically —
`app.function('myFunc')` registers `/api/functions/myFunc` at runtime,
in addition to the core `/api/messages` endpoint. The adapter needs a
`registerRoute` method so that both `HttpServer` and `app.function()`
can tell it what paths to listen on.

### Optional methods

`start`/`stop` are optional — serverless adapters (Vercel, Lambda) don't
need them. `serveStatic` is optional — only needed for tab hosting.

`HttpMethod` is currently just `'POST'` (the only method the Teams
protocol uses). It may expand to a union if needed.

## Backward Compat

We've updated `HttpPlugin` to basically use `HttpServer` with an
`ExpressAdapter` internally for backward compat. I don't think this
should lead to any breaking changes (even if someone passes in their own
`HttpPlugin`). (Tested BotBuilderPlugin, from examples, and it worked
without any changes).
However, it should be noted that I marked HttpPlugin as deprecated in
this PR, so it should be discouraged going forward, and after the next
few versions, it'll be removed.

## Testing

I tested by running the following examples:

1. Echo bot
2. Devtools
3. BotBuilder
4. HttpPlugin
5. Tabs
6. AI (streaming and regular completions)



skip-test-verification (added manifest for tabs)



#### PR Dependency Tree


* **PR #424**
  * **PR #433** 👈
    * **PR #442**


#### PR Dependency Tree


* **PR #424**
  * **PR #433** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
corinagum
corinagum previously approved these changes Mar 17, 2026
Copy link
Copy Markdown
Collaborator

@corinagum corinagum left a comment

Choose a reason for hiding this comment

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

lgtm, tentative approval - what's the plan for the teams new scaffolding testing?

lilyydu and others added 3 commits March 17, 2026 13:30
- property was incorrectly named `objectId` instead of `aadObjectId`,
field was being dropped during serialization when the `meetingEnd` event
was triggered

- schema that we are using:

https://learn.microsoft.com/en-us/dotnet/api/microsoft.bot.schema.teams.teamschannelaccount?view=botbuilder-dotnet-stable

- equivalent python fix: microsoft/teams.py#300
- dotnet has it correctly set

Co-authored-by: lilydu <lilydu+odspmdb@microsoft.com>
…e-aamirj/direct-deps

Resolve conflicts in examples/botbuilder/package.json,
packages/botbuilder/package.json, and package-lock.json by
taking the PR branch changes (remove redundant internal peer
deps, pin botbuilder to exact version).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Replace relative dist path imports of ILogger with @microsoft/teams.common
- Add @microsoft/teams.common as dependency in examples/ai
- Remove unused Dependency import from mcp plugin

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@heyitsaamir
Copy link
Copy Markdown
Collaborator Author

lgtm, tentative approval - what's the plan for the teams new scaffolding testing?

Verified, works :)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
corinagum
corinagum previously approved these changes Mar 23, 2026
@heyitsaamir heyitsaamir dismissed corinagum’s stale review March 23, 2026 19:35

The merge-base changed after approval.

corinagum
corinagum previously approved these changes Mar 23, 2026
@heyitsaamir heyitsaamir dismissed corinagum’s stale review March 23, 2026 19:50

The merge-base changed after approval.

Copy link
Copy Markdown
Collaborator

@corinagum corinagum left a comment

Choose a reason for hiding this comment

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

lgtm, please work

@heyitsaamir heyitsaamir merged commit 549772c into main Mar 23, 2026
8 checks passed
@heyitsaamir heyitsaamir deleted the aamirj/direct-deps branch March 23, 2026 19:51
@heyitsaamir heyitsaamir mentioned this pull request Mar 25, 2026
2 tasks
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