Update quoted replies & new quotes features#389
Conversation
c03ea73 to
fdbb1bd
Compare
There was a problem hiding this comment.
Pull request overview
This PR updates the SDK’s quoted-reply support to use Teams’ modern quotedReply entity + <quoted messageId="..."/> placeholder format, adds APIs for creating/reading quoted replies, and introduces tests + a sample app to exercise the new behavior.
Changes:
- Introduces
QuotedReplyEntity/QuotedReplyDataand registers it in the entity JSON converter. - Adds
GetQuotedMessages()andAddQuotedReply(...)onMessageActivity, and updatesContext.Reply(...)plus newContext.QuoteReply(...)helpers to stamp quoted-reply metadata. - Adds unit tests and a new
Samples.QuotedRepliesproject demonstrating end-to-end usage.
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| Tests/Microsoft.Teams.Apps.Tests/Microsoft.Teams.Apps.Tests.csproj | Suppresses experimental diagnostic for quoted replies in Apps tests. |
| Tests/Microsoft.Teams.Apps.Tests/Contexts/ContextQuotedReplyTests.cs | Adds context-level tests validating Reply() / QuoteReply() stamping behavior. |
| Tests/Microsoft.Teams.Api.Tests/Microsoft.Teams.Api.Tests.csproj | Suppresses experimental diagnostic for quoted replies in Api tests. |
| Tests/Microsoft.Teams.Api.Tests/Json/Entities/QuotedReplyEntity.json | Adds golden JSON payload for quoted reply entity serialization tests. |
| Tests/Microsoft.Teams.Api.Tests/Entities/QuotedReplyEntityTests.cs | Adds serialization/deserialization and helper API tests for quoted replies. |
| Samples/Samples.QuotedReplies/appsettings.json | Adds sample configuration for the quoted replies demo app. |
| Samples/Samples.QuotedReplies/Samples.QuotedReplies.csproj | Adds new sample project wired to experimental quoted-reply APIs. |
| Samples/Samples.QuotedReplies/README.md | Documents sample commands and quoted-reply usage patterns. |
| Samples/Samples.QuotedReplies/Properties/launchSettings.TEMPLATE.json | Adds launch profile template for the new sample. |
| Samples/Samples.QuotedReplies/Program.cs | Implements demo bot behavior showing read/write quoted replies. |
| Libraries/Microsoft.Teams.Apps/Contexts/Context.Send.cs | Updates Reply() quoting behavior and adds QuoteReply() context APIs. |
| Libraries/Microsoft.Teams.Api/Entities/QuotedReplyEntity.cs | Adds the new quoted reply entity types. |
| Libraries/Microsoft.Teams.Api/Entities/Entity.cs | Registers quotedReply in entity JSON (de)serialization. |
| Libraries/Microsoft.Teams.Api/Activities/Message/MessageActivity.cs | Adds GetQuotedMessages() and AddQuotedReply(...) builder. |
| Libraries/Microsoft.Teams.Api/Activities/Activity.cs | Removes WithReplyToId(), updates ToQuoteReply() and marks it obsolete. |
Comments suppressed due to low confidence (1)
Libraries/Microsoft.Teams.Api/Activities/Activity.cs:201
WithReplyToId(string)was removed fromActivity. If this library has external consumers, removing a public fluent setter is a breaking change; it may be safer to keep the method and mark it[Obsolete](or provide a compatible alternative) to avoid forcing downstream recompiles/changes.
public virtual Activity WithId(string value)
{
Id = value;
return this;
}
public virtual Activity WithChannelId(ChannelId value)
{
ChannelId = value;
return this;
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 15 out of 15 changed files in this pull request and generated 4 comments.
Comments suppressed due to low confidence (1)
Libraries/Microsoft.Teams.Api/Activities/Activity.cs:203
WithReplyToId(string)was removed from the fluentActivityAPI. Since the rest ofWithXyz(...)methods remain, removing this public method is a source/binary breaking change for consumers that used it to setReplyToId. Consider keeping it (at least temporarily) as an[Obsolete]shim that assignsReplyToIdand returnsthis, or ensure the breaking change is intentional and called out in release notes/versioning.
public virtual Activity WithId(string value)
{
Id = value;
return this;
}
public virtual Activity WithChannelId(ChannelId value)
{
ChannelId = value;
return this;
}
public virtual Activity WithFrom(Account value)
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
67ba64a to
52c5002
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 15 out of 15 changed files in this pull request and generated 5 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 15 out of 15 changed files in this pull request and generated 5 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 15 out of 15 changed files in this pull request and generated 3 comments.
Comments suppressed due to low confidence (1)
Libraries/Microsoft.Teams.Api/Activities/Activity.cs:201
WithReplyToId(string value)was removed fromActivity, which is a breaking change for consumers relying on the fluent builder API. If the goal is to deprecate this surface, consider keeping the method and marking it[Obsolete](or providing an alternative builder) rather than removing it outright, and ensure the change is reflected in the public API/semver strategy.
public virtual Activity WithId(string value)
{
Id = value;
return this;
}
public virtual Activity WithChannelId(ChannelId value)
{
ChannelId = value;
return this;
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
ed385fb to
90cbb31
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 15 out of 15 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (1)
Libraries/Microsoft.Teams.Api/Activities/Activity.cs:201
- Removing the public fluent helper
WithReplyToId(...)is a breaking change for consumers (especially those using the builder-style API), even ifReplyToIdremains. Consider keeping the method as[Obsolete](forwarding toReplyToId) for at least one release, or ensure this ships with an appropriate major version bump/release note.
public virtual Activity WithId(string value)
{
Id = value;
return this;
}
public virtual Activity WithChannelId(ChannelId value)
{
ChannelId = value;
return this;
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
334edf4 to
9243e81
Compare
9243e81 to
b8a221b
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 15 out of 15 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (1)
Libraries/Microsoft.Teams.Api/Activities/Activity.cs:202
- Removing the public
WithReplyToId(string)fluent builder is a source/binary breaking change for consumers, especially since theReplyToIdproperty still exists on the model. If this removal is intentional, consider leaving an[Obsolete]forwarding method for at least one release (or documenting the migration path) to reduce upgrade friction.
public virtual string GetPath() => string.Join(".", ["Activity", Type.ToPrettyString()]);
public virtual Activity WithId(string value)
{
Id = value;
return this;
}
public virtual Activity WithChannelId(ChannelId value)
{
ChannelId = value;
return this;
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
b8a221b to
a7a23dd
Compare
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
- Reply<T>() now delegates to QuoteReply<T>() when Activity.Id is set - QuoteReply<T>() only stamps entity + placeholder for MessageActivity - Remove redundant conversation copy (AspNetCorePlugin.Send overwrites it) - Update test fixture time values to IC3 epoch format Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
a7a23dd to
b8cafea
Compare
| @@ -192,12 +194,6 @@ public virtual Activity WithId(string value) | |||
| return this; | |||
| } | |||
|
|
|||
| public Task<T> Reply<T>(T activity, CancellationToken cancellationToken = default) where T : IActivity | ||
| { | ||
| activity.Conversation = Ref.Conversation.Copy(); | ||
| activity.Conversation.Id = Ref.Conversation.ThreadId; | ||
|
|
||
| if (activity is MessageActivity message) | ||
| if (Activity.Id != null) | ||
| { | ||
| message.Text = string.Join("\n", [ | ||
| Activity.ToQuoteReply(), | ||
| message.Text != string.Empty ? $"<p>{message.Text}</p>" : string.Empty | ||
| ]); | ||
| return Quote(Activity.Id, activity, cancellationToken); |
| <Project Sdk="Microsoft.NET.Sdk.Web"> | ||
|
|
||
| <PropertyGroup> | ||
| <TargetFramework>net10.0</TargetFramework> | ||
| <Nullable>enable</Nullable> | ||
| <ImplicitUsings>enable</ImplicitUsings> | ||
| <NoWarn>$(NoWarn);ExperimentalTeamsQuotedReplies</NoWarn> | ||
| </PropertyGroup> |
## Summary Brings `main` into `releases/v2` for the 2.0.7 Libraries release, with quoted-replies (PR #389) excluded. Scope is **Legacy / Libraries only** — `core/` is not in scope for this release. Single squashed commit. ## What's in this release - All commits merged to main since 2.0.6 (the previous Libraries release), notably: - **Reactions GA** (PR #509) — `[Experimental("ExperimentalTeamsReactions")]` removed across Libraries - Prompt Preview Support (PR #433) - MCP server auth security hardening (PR #448) - Various dependency bumps and bug fixes ## What's NOT in this release **Quoted-replies feature (PR #389) is excluded:** - `MessageActivity.AddQuote()`, `PrependQuote()` builder methods — absent - `Context.Reply()` quote-aware behavior — reverted to legacy `replyToId` + blockquote - `Samples/Samples.Quoting` — removed - QR-related tests — removed **Kept intentionally:** - `QuotedReplyEntity` and `QuotedReplyData` types stay in `Microsoft.Teams.Api.Entities` (still annotated `[Experimental("ExperimentalTeamsQuotedReplies")]`) so inbound activities carrying `quotedReply` entities still parse - `MessageActivity.AddTargetedMessageInfo` runtime strip-QR logic — preserves prompt-preview cleanup - Entity.cs serializer cases for `quotedReply` (with existing `#pragma warning disable ExperimentalTeamsQuotedReplies`) ## Version `version.json`: `2.0.6` → `2.0.7` ## Scope **Libraries only.** `core/` (the `releases/core` track) is not affected by this PR — when this prep merges to `releases/v2`, the `core/` tree may appear in the branch as part of the merge, but the Legacy publish pipeline only builds and ships from `Libraries/`, `Samples/`, and `Tests/`. ## Test plan - [x] `dotnet build Microsoft.Teams.sln --configuration Release` — 0 errors, 5 pre-existing warnings - [x] `dotnet test Microsoft.Teams.sln --configuration Release` — all suites pass across net8.0 and net10.0 (Api 407+407, Apps 136+136, Common 63+63, AI 17+17, McpClient 9+9, Graph 3+3, AspNetCore 25+25 with 1 pre-existing skip) - [x] `dotnet pack` — nupkgs produced; consumer install verified (fresh console project, `dotnet add package Microsoft.Teams.Apps --prerelease`, `dotnet run` confirms `QuotedReplyEntity` ships, `AddQuote`/`PrependQuote` absent, `AddTargetedMessageInfo` present, `ReactionClient.AddAsync`/`DeleteAsync` present) - [ ] Teams.NET-ESRP pipeline with `packageSet=Legacy`, `publishType=Public` (post-merge) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
WithReplyToId(), updateToQuoteReply()and mark it[Obsolete]QuotedReplyEntitywithQuotedReplyData(MessageIdrequired,SenderId/SenderName/Preview/Time/IsReplyDeleted/ValidatedMessageReferenceoptional); register in Entity JSON converterGetQuotedMessages()onMessageActivityto read inbound quoted reply entitiesAddQuote(messageId, text?)builder onMessageActivity(appends quote placeholder + optional text)PrependQuote(messageId)onMessageActivity(prepends quote placeholder before existing text, used internally byReply()/Quote())Reply()to delegate toQuote()whenActivity.Idis set; remove conversation copy (redundant —AspNetCorePlugin.Send()handles it) and;messageid=stripping (redundant — APX normalizes server-side)Quote()on context — sends a message with a quoted message reference prepended to the text. Entity + placeholder only stamped forMessageActivity.[Experimental("ExperimentalTeamsQuotedReplies")]Reply()andQuote()