Support resetting response streams#495
Merged
heyitsaamir merged 12 commits intoJul 7, 2026
Merged
Conversation
Add a reset option to stream close so handlers can finalize one streamed message and reuse the same stream instance for a later streamed message. Update the stream example with a multi-stream flow and cover the lifecycle in HttpStream tests. Co-authored-by: Copilot <[email protected]>
Include the formatted-messaging workspace member and refreshed dependency metadata in uv.lock. Co-authored-by: Copilot <[email protected]>
Contributor
There was a problem hiding this comment.
Pull request overview
Adds a reset option to the streaming close lifecycle so a handler can finalize one streamed response, send a non-streamed message (or card), and then reuse the same stream instance for a later streamed segment—addressing the “multiple streams in one handler” scenario without reaching into private context internals.
Changes:
- Extend
StreamerProtocol.close/HttpStream.closewithreset: bool = Falseto allow reusing a stream instance after finalizing a streamed message. - Switch
HttpStream.on_closeto persistent subscriptions so close handlers can fire for each finalized streamed message. - Update the streaming example to demonstrate a “multi-stream” flow and add a regression test for reset + reuse.
Reviewed changes
Copilot reviewed 5 out of 6 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
uv.lock |
Adds missing workspace member metadata and refreshes dependency metadata. |
packages/apps/tests/test_http_stream.py |
Adds coverage for closing with reset=True and reusing the same stream instance. |
packages/apps/src/microsoft_teams/apps/plugins/streamer.py |
Updates the public StreamerProtocol API/docs to include close(reset=...). |
packages/apps/src/microsoft_teams/apps/http_stream.py |
Implements close(reset=...) semantics and allows multiple close events via .on("close", ...). |
examples/stream/src/main.py |
Demonstrates interleaving two stream segments with a normal message in between. |
examples/stream/README.md |
Documents how to run the multi-stream demo. |
Add a standalone simple-card path to the stream example for validating Adaptive Card delivery independently of streaming reset behavior. Co-authored-by: Copilot <[email protected]>
Update the multi-stream stream example to send the simple Adaptive Card between streamed response segments. Co-authored-by: Copilot <[email protected]>
Update the multi-stream example to emit the Adaptive Card through ctx.stream before close(reset=True), so the card is part of the first stream's final message. Co-authored-by: Copilot <[email protected]>
Make close finalize the current streamed message while a later emit or update starts a new stream cycle on the same instance. Keep repeated close calls idempotent until another emit occurs. Co-authored-by: Copilot <[email protected]>
lilyydu
reviewed
Jul 2, 2026
Rename the per-message reset helper and add a separate helper for preparing the stream instance for the next stream cycle. Co-authored-by: Copilot <[email protected]>
Prefer the HttpStream.closed property where the code is checking whether the current stream cycle has been finalized. Co-authored-by: Copilot <[email protected]>
Align protocol documentation and example text with the emit-after-close stream cycle behavior. Co-authored-by: Copilot <[email protected]>
Replace AdaptiveCard.model_validate in the stream example with the builder-style AdaptiveCard and TextBlock classes. Co-authored-by: Copilot <[email protected]>
rajan-chari
reviewed
Jul 7, 2026
rajan-chari
approved these changes
Jul 7, 2026
rajan-chari
left a comment
Contributor
There was a problem hiding this comment.
LGTM. My comment is a clarification question.
lilyydu
approved these changes
Jul 7, 2026
…-response-streams # Conflicts: # packages/apps/src/microsoft_teams/apps/http_stream.py
lilyydu
approved these changes
Jul 7, 2026
Wrap test close-event waits with explicit timeouts so the test fails clearly instead of hanging if the close event is not emitted. Co-authored-by: Copilot <[email protected]>
lilyydu
approved these changes
Jul 7, 2026
pull Bot
pushed a commit
to Mattlk13/teams.ts
that referenced
this pull request
Jul 9, 2026
…ndling (microsoft#635) ## Summary Ports two merged **teams.py** PRs to TypeScript: - microsoft/teams.py#453 — streaming error handling for the 2-minute timeout & "content stream not allowed" - microsoft/teams.py#495 — support resetting response streams (reuse after `close()`) ## Streaming error handling (port of microsoft#453) - Add `TerminalStreamError`, `StreamTimedOutError`, and `StreamNotAllowedError` (in `types/streamer.ts`). - `HttpStream.send()` now inspects the `403` response body and maps it to the right error instead of always treating a `403` as canceled: - `exceeded streaming time` → `StreamTimedOutError` (marks `timedOut`) - `cancel` → `StreamCancelledError` - `not allowed` → `StreamNotAllowedError` - anything else / empty body → `TerminalStreamError` - On a 2-minute streaming timeout, the stream finalizes by **updating the original message in place** — it drops the `streaminfo` entity and stream `channelData` so the send routes through *update* (reusing the id) rather than posting a duplicate. - Chunk sends swallow a timeout mid-stream; `close()` then sends the buffered content as a plain final message. - `retry` gains a `nonRetryable` option so terminal stream errors are not retried. ## Reusable streams (port of microsoft#495) - Emitting or updating after `close()` reopens the stream on the **same instance**, starting a new streamed message. - `close()` is idempotent until the next emit/update. - `app.process.ts` close listener switched from `once` → `on` so each reused-stream close fires. ## Example Adds `examples/stream`, mirroring the teams.py example's final state: - default message → single-stream demo with suggested actions - `simple-card` → sends a minimal Adaptive Card outside the streaming flow - `multi-stream` → emits an Adaptive Card as stream 1's final message, `close()`s, then reuses `ctx.stream` for a second streamed response ## Tests - `packages/apps` http-stream spec extended: 403 message→error mapping (parametrized), empty-body 403 → terminal error, final-send timeout updates in place, mid-stream timeout updates in place, and emit-after-close stream reuse. - Full `packages/apps` suite passes (294 tests); `tsc` and eslint clean across the changed library files and the new example. ## Notes - The Python-only `uv.lock` change was not ported. - `examples/stream/.env` is gitignored (no credentials committed). --------- Co-authored-by: Copilot <[email protected]> Co-authored-by: Copilot Autofix powered by AI <[email protected]>
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.
Summary
HttpStreamreusable after close: a lateremit()orupdate()starts a new streamed message cycle on the same stream instanceclose()calls idempotent until another emit/update occursmulti-streamflow that emits an Adaptive Card in the first stream final message, closes, then reusesctx.streamfor a second streamed segmentuv.lockto include the missingformatted-messagingworkspace member and dependency metadataContext
This addresses the scenario in #489 where a handler needs to:
Before this change, the only way to do that was to close
ctx.streamand then reach into the privatectx._activity_sender.create_stream(...)API to create another stream.Motivation
This is useful for conversational AI and tool-using bot flows where a response is not always one uninterrupted text stream.
For example, a bot may need to stream an explanation while it reasons or searches, include a structured checkpoint message, approval prompt, or card, and then continue streaming the final answer after that intermediate interaction.
Without a public lifecycle API, developers either have to avoid streaming for these richer flows or access private SDK internals to create a fresh stream. Reopening the stream on the next emit gives them a supported way to split one handler's response into multiple sequential streamed messages while keeping the SDK's one-stream-object model.
Alternatives considered
ctx.new_stream()We first considered adding a public
ctx.new_stream()method that would create and return another stream object.That solves the private API issue, but it makes the model feel like a handler can manage multiple stream objects at once. In practice, Teams appears to behave more reliably when there is only one active stream at a time. Multiple stream objects also complicate ownership, finalization, event handler registration, and retry behavior.
Context-managed multiple streams
We also considered having
ActivityContexttrack all streams created during a handler and close them all at the end.That keeps cleanup centralized, but it still encourages multiple open streams during one handler. It also makes ordering less explicit: a stream could remain active while a normal message or a second stream is sent, which is exactly the behavior that seemed risky.
close(reset=True)We also tried an explicit
close(reset=True)option. It worked, but it made the API more awkward and introduced a flag for what is conceptually the next stream cycle.The approach in this PR keeps
close()simple: it finalizes the current streamed message. Repeatedclose()calls remain idempotent. If the caller later emits or updates, the stream starts a new cycle automatically.Example
The stream example now includes a
multi-streampath:Screen.Recording.2026-07-01.at.6.08.51.PM.mov
This demonstrates the intended lifecycle: one streamed message is finalized, and the same
ctx.streaminstance is reused for the next streamed message when the handler emits again.Addresses #489