feat(apps): support resetting response streams and streaming error handling#635
Merged
Conversation
…ndling Port of teams.py PRs #495 and #453 to TypeScript. Streaming error handling (#453): - Add TerminalStreamError, StreamTimedOutError, and StreamNotAllowedError - Map the 403 error body to the right error (timed out / canceled / not allowed / terminal) instead of always treating a 403 as canceled - On a 2-minute streaming timeout, finalize by updating the original message in place (drop streamInfo + stream channelData) rather than posting a duplicate - Add a nonRetryable option to the retry util so terminal stream errors are not retried Reusable streams (#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 processor close listener switched from once to on so each reused close fires Add the stream example demonstrating single-stream, simple-card, and multi-stream (Adaptive Card + close + reuse) flows. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds the @examples/stream workspace entry so `npm ci` stays in sync with package.json. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Ports streaming behavior improvements from teams.py into @microsoft/teams.apps, adding richer handling of terminal streaming failures (403 variants), support for reusing a stream instance after close(), and a new examples/stream sample to demonstrate multi-stream flows.
Changes:
- Add terminal streaming error types and route 403 streaming responses to specific errors (timeout/cancel/not-allowed/terminal).
- Make
HttpStream.close()idempotent and allow emit/update after close to restart streaming on the same instance; update processor close listener accordingly. - Add a new
examples/streamsample showcasing single-stream, simple-card, and multi-stream usage.
Reviewed changes
Copilot reviewed 13 out of 16 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/apps/src/utils/promises/retry.ts | Adds nonRetryable option to stop retrying specific error classes. |
| packages/apps/src/types/streamer.ts | Introduces terminal stream error classes and adds closed to the streamer contract. |
| packages/apps/src/http/http-stream.ts | Implements timeout/not-allowed error mapping, idempotent/reusable close behavior, and finalization behavior after timeout. |
| packages/apps/src/http/http-stream.spec.ts | Expands test coverage for 403 error mapping, timeout finalization behavior, and stream reuse after close. |
| packages/apps/src/app.process.ts | Switches close listener to .on('close') to support reused streams firing close multiple times. |
| examples/stream/turbo.json | Adds turbo task config for the new stream example. |
| examples/stream/tsconfig.json | Adds TypeScript config for the new stream example. |
| examples/stream/src/index.ts | Implements the stream demo app (single-stream, simple-card, multi-stream). |
| examples/stream/README.md | Documents how to run and test the stream example. |
| examples/stream/package.json | Adds the example package definition and scripts. |
| examples/stream/eslint.config.js | Adds eslint config wiring for the example. |
| examples/stream/appPackage/manifest.json | Adds a Teams app manifest for the example bot. |
| examples/stream/.gitignore | Adds ignore rules for generated/dev files in the example. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
…reamError The 'Content stream is not allowed on an already completed streamed message' 403 shares the ContentStreamNotAllowed error code and the 'not allowed' substring with the generic stream-not-allowed error, so it was incorrectly mapped to StreamNotAllowedError. Guard the 'not allowed' branch against 'completed streamed message' so this terminal case falls through to TerminalStreamError. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2b30180 to
44a9e86
Compare
corinagum
approved these changes
Jul 9, 2026
The main merge (#634, flatten client method chains) replaced the chained client.conversations.activities().create/.update API with flattened client.conversations.createActivity/updateActivity. Six stream tests still mocked the old chain, failing with 'client.conversations.activities is not a function'. Update the mocks to the flattened methods and their new signatures (conversationId is now the first argument). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
corinagum
approved these changes
Jul 9, 2026
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
Ports two merged teams.py PRs to TypeScript:
close())Streaming error handling (port of #453)
TerminalStreamError,StreamTimedOutError, andStreamNotAllowedError(intypes/streamer.ts).HttpStream.send()now inspects the403response body and maps it to the right error instead of always treating a403as canceled:exceeded streaming time→StreamTimedOutError(markstimedOut)cancel→StreamCancelledErrornot allowed→StreamNotAllowedErrorTerminalStreamErrorstreaminfoentity and streamchannelDataso the send routes through update (reusing the id) rather than posting a duplicate.close()then sends the buffered content as a plain final message.retrygains anonRetryableoption so terminal stream errors are not retried.Reusable streams (port of #495)
close()reopens the stream on the same instance, starting a new streamed message.close()is idempotent until the next emit/update.app.process.tsclose listener switched fromonce→onso each reused-stream close fires.Example
Adds
examples/stream, mirroring the teams.py example's final state:simple-card→ sends a minimal Adaptive Card outside the streaming flowmulti-stream→ emits an Adaptive Card as stream 1's final message,close()s, then reusesctx.streamfor a second streamed responseTests
packages/appshttp-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.packages/appssuite passes (294 tests);tscand eslint clean across the changed library files and the new example.Notes
uv.lockchange was not ported.examples/stream/.envis gitignored (no credentials committed).