Skip to content

Commit a2f5ca5

Browse files
Add word excel document comment notifications (#542)
* just to commit. * add word comment notifications.
1 parent 7078203 commit a2f5ca5

2 files changed

Lines changed: 114 additions & 2 deletions

File tree

.github/CODEOWNERS

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,17 @@
5454
/samples/typescript/quickstart/create-agent/src/quickstart-create-agent.ts @microsoft-foundry/AI-Platform-Docs
5555
/samples/typescript/quickstart/responses/src/quickstart-responses.ts @microsoft-foundry/AI-Platform-Docs
5656

57+
#### Additional ownership entries (added via issue triage) ##############################################
58+
# Routing for sample paths that were uncovered during issue triage.
59+
# Owners chosen from CODEOWNERS pattern of peer directories and from git log of top contributors.
60+
61+
# TS quickstart agent-service — peer of chat-with-agent / create-agent / responses
62+
/samples/typescript/quickstart/agent-service/ @microsoft-foundry/AI-Platform-Docs
63+
64+
# Infrastructure (bicep) — networked agent setup templates
65+
/infrastructure/infrastructure-setup-bicep/01-connections/apim/ @meerakurup
66+
/infrastructure/infrastructure-setup-bicep/15-private-network-standard-agent-setup/ @haflidif
67+
/infrastructure/infrastructure-setup-bicep/16-private-network-standard-agent-apim-setup/ @meerakurup
68+
69+
# Infrastructure (terraform) — BYO-VNet variant
70+
/infrastructure/infrastructure-setup-terraform/15b-private-network-standard-agent-setup-byovnet/ @deeikele

samples/csharp/foundry-autopilot-agent/src/hello_world_a365_agent/AgentLogic/ResponsesApi/ResponsesApiAgentLogicService.cs

Lines changed: 100 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ namespace HelloWorldA365.AgentLogic.ResponsesApi;
33
using Azure.Core;
44
using Azure.Identity;
55
using HelloWorldA365.Models;
6+
using Microsoft.Agents.A365.Notifications;
67
using Microsoft.Agents.A365.Notifications.Models;
78
using Microsoft.Agents.Builder;
89
using Microsoft.Agents.Builder.State;
@@ -117,10 +118,107 @@ public async Task HandleEmailNotificationAsync(ITurnContext turnContext, ITurnSt
117118
await turnContext.SendActivityAsync(responseActivity);
118119
}
119120

120-
public Task HandleCommentNotificationAsync(ITurnContext turnContext, ITurnState turnState, AgentNotificationActivity commentEvent)
121+
public async Task HandleCommentNotificationAsync(ITurnContext turnContext, ITurnState turnState, AgentNotificationActivity commentEvent)
121122
{
122123
_logger.LogInformation("Processing comment notification (Responses API)");
123-
return Task.CompletedTask;
124+
125+
var comment = commentEvent.WpxCommentNotification;
126+
if (comment == null)
127+
{
128+
_logger.LogWarning("Comment notification received without WpxComment payload; skipping.");
129+
return;
130+
}
131+
132+
// The document the comment lives on is delivered as the first attachment on the activity.
133+
var attachments = turnContext.Activity.Attachments;
134+
var contentUrl = attachments?.FirstOrDefault()?.ContentUrl;
135+
if (string.IsNullOrEmpty(contentUrl))
136+
{
137+
_logger.LogWarning(
138+
"Comment notification for CommentId={CommentId} on DocumentId={DocumentId} has no attachment ContentUrl; cannot fetch document content.",
139+
comment.CommentId,
140+
comment.DocumentId);
141+
return;
142+
}
143+
144+
// Figure out which Office product (and therefore which MCP server) to use.
145+
// The sub-channel is set by the OnAgenticWord/Excel/PowerPointNotification routers.
146+
var subChannel = turnContext.Activity.ChannelId?.SubChannel ?? string.Empty;
147+
string productLabel;
148+
string mcpServerName;
149+
if (subChannel.Equals(SubChannels.AgentsWordSubChannel, StringComparison.OrdinalIgnoreCase))
150+
{
151+
productLabel = "Word";
152+
mcpServerName = "mcp_WordServer";
153+
}
154+
else if (subChannel.Equals(SubChannels.AgentsExcelSubChannel, StringComparison.OrdinalIgnoreCase))
155+
{
156+
productLabel = "Excel";
157+
mcpServerName = "mcp_ExcelServer";
158+
}
159+
else if (subChannel.Equals(SubChannels.AgentsPowerPointSubChannel, StringComparison.OrdinalIgnoreCase))
160+
{
161+
productLabel = "PowerPoint";
162+
mcpServerName = "mcp_PowerPointServer";
163+
}
164+
else
165+
{
166+
// Fall back to inferring from the file extension on the content URL.
167+
(productLabel, mcpServerName) = InferProductFromUrl(contentUrl);
168+
}
169+
170+
var commenter = commentEvent.From?.Name ?? commentEvent.From?.Id ?? "the commenter";
171+
var commentText = (turnContext.Activity.Text ?? string.Empty).Trim();
172+
var commentSnippet = string.IsNullOrEmpty(commentText) ? "(no comment text)" : commentText;
173+
var conversationId = $"comment:{comment.DocumentId ?? "unknown-doc"}:{comment.CommentId ?? "unknown-comment"}";
174+
175+
var prompt = $"""
176+
You have been @-mentioned in a {productLabel} comment and must reply to it.
177+
178+
Use the {mcpServerName} MCP tools to do the following, in order:
179+
1. Call GetDocumentContent with the sharing URL below to read the document and
180+
locate the text that the comment refers to.
181+
2. Call ReplyToComment with commentId="{comment.CommentId}" to post your reply
182+
directly on the thread. Do NOT respond via chat or email — the reply must be
183+
posted through the {mcpServerName} ReplyToComment tool so it shows up on the
184+
comment thread in the document.
185+
186+
Keep the reply concise, helpful, and grounded in the actual document content.
187+
Format the reply as plain text (the comment thread does not render HTML).
188+
189+
Document URL: {contentUrl}
190+
DocumentId: {comment.DocumentId}
191+
CommentId: {comment.CommentId}
192+
ParentCommentId: {comment.ParentCommentId ?? "(none — this is a top-level comment)"}
193+
Commenter: {commenter}
194+
Comment text: {commentSnippet}
195+
""";
196+
197+
var response = await InvokeResponsesApiAsync(prompt, conversationId);
198+
199+
// The reply is posted on the comment thread by the MCP server's ReplyToComment tool,
200+
// so there is nothing to send back through the activity protocol here. The model's
201+
// final text (if any) is logged for diagnostics only.
202+
_logger.LogInformation(
203+
"Comment reply flow finished for {Product} CommentId={CommentId}. Model output (for diagnostics only): {Response}",
204+
productLabel,
205+
comment.CommentId,
206+
string.IsNullOrWhiteSpace(response) ? "(empty — reply was posted via MCP tool)" : response);
207+
}
208+
209+
private static (string Product, string McpServer) InferProductFromUrl(string url)
210+
{
211+
var lower = url.ToLowerInvariant();
212+
if (lower.Contains(".xlsx") || lower.Contains(".xlsm") || lower.Contains(".xlsb"))
213+
{
214+
return ("Excel", "mcp_ExcelServer");
215+
}
216+
if (lower.Contains(".pptx") || lower.Contains(".ppt"))
217+
{
218+
return ("PowerPoint", "mcp_PowerPointServer");
219+
}
220+
// Default to Word — covers .docx/.doc and the unknown case.
221+
return ("Word", "mcp_WordServer");
124222
}
125223

126224
public Task HandleInstallationUpdateAsync(ITurnContext turnContext, ITurnState turnState, AgentNotificationActivity installationEvent)

0 commit comments

Comments
 (0)