Skip to content

feat: add multiple messages pattern to dotnet agent-framework sample#246

Open
sellakumaran wants to merge 4 commits intomainfrom
users/sellak/multiple-responses
Open

feat: add multiple messages pattern to dotnet agent-framework sample#246
sellakumaran wants to merge 4 commits intomainfrom
users/sellak/multiple-responses

Conversation

@sellakumaran
Copy link
Contributor

Demonstrates the recommended pattern for sending multiple discrete messages to a Teams user from a single agent turn:

  • Immediate ack via SendActivityAsync before LLM processing
  • Typing indicator loop (refreshed every 4s, properly awaited in finally)
  • StreamingResponse retained as best-effort for non-Teams/WebChat clients

Documents the pattern in README with code examples and notes on typing indicator limitations (1:1 and small group chats only, ~5s timeout).

Demonstrates the recommended pattern for sending multiple discrete messages
to a Teams user from a single agent turn:
- Immediate ack via SendActivityAsync before LLM processing
- Typing indicator loop (refreshed every 4s, properly awaited in finally)
- StreamingResponse retained as best-effort for non-Teams/WebChat clients

Documents the pattern in README with code examples and notes on typing
indicator limitations (1:1 and small group chats only, ~5s timeout).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions
Copy link

github-actions bot commented Mar 12, 2026

⚠️ Deprecation Warning: The deny-licenses option is deprecated for possible removal in the next major release. For more information, see issue 997.

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

OpenSSF Scorecard

PackageVersionScoreDetails
npm/@types/express ^4.17.25 UnknownUnknown
npm/@types/node ^20.19.37 UnknownUnknown

Scanned Files

  • nodejs/langchain/quickstart-before/package.json

- Add ArgumentNullException guard for turnContext before InvokeObservedAgentOperation
- Expand empty OperationCanceledException catch block with explanatory comment

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…) across all dotnet and nodejs samples

Implements issue #268 pattern — send an immediate ack message, run a typing
indicator loop every ~4s, then deliver the LLM response as a second discrete
message. All changes include README documentation.

dotnet/semantic-kernel:
- Send ack + single typing indicator in MessageActivityAsync before GetAgent365Agent
  so they arrive before the streaming connection opens
- Read OboAuthHandlerName from config (was hardcoded)
- Register InstallationUpdate for both agentic and non-agentic identities
- Add SKIP_TOOLING_ON_ERRORS to launchSettings for local dev

nodejs (openai, langchain, langchain/quickstart-before, claude, vercel-sdk,
        devin, perplexity, copilot-studio):
- Add ack sendActivity before LLM call
- Add setInterval typing loop (~4s); stopped in finally block
- Add/fix InstallationUpdate handler where missing (copilot-studio, quickstart-before)

nodejs/langchain/quickstart-before (additional pre-existing fixes):
- Fix build errors: instructions → systemPrompt (langchain 1.2.32 API change)
- Add @types/express and @types/node devDependencies
- Add Azure OpenAI support alongside standard OpenAI in client.ts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Python (openai, claude, crewai, google-adk, agent-framework): add immediate
  ack message ("Got it — working on it…"), explicit typing indicator await
  before create_task, and asyncio background loop refreshing every 4s
- Node.js (openai, claude, langchain, vercel-sdk, copilot-studio): remove
  startTypingTimer: true (fires before ack, wrong order) and add explicit
  await sendActivity(typing) after ack, before setInterval loop
- dotnet agent-framework: add explicit typing await on main thread before
  Task.Run, fix loop order (Delay first then send), move null guard to top
  of OnMessageAsync (fixes Copilot review finding)
- crewai: fix pysqlite3-binary to Linux-only (no Windows wheel), add Azure
  LLM config (azure/gpt-4o) to agents.yaml
- All Python READMEs: add "Sending Multiple Messages in Teams" section with
  correct code example including explicit typing line

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@sellakumaran sellakumaran marked this pull request as ready for review March 14, 2026 01:55
@sellakumaran sellakumaran requested a review from a team as a code owner March 14, 2026 01:55
Copilot AI review requested due to automatic review settings March 14, 2026 01:55
typing_task.cancel()
try:
await typing_task
except asyncio.CancelledError:
typing_task.cancel()
try:
await typing_task
except asyncio.CancelledError:
typing_task.cancel()
try:
await typing_task
except asyncio.CancelledError:
typing_task.cancel()
try:
await typing_task
except asyncio.CancelledError:
// Each SendActivityAsync call produces a discrete Teams message, enabling the multiple-messages pattern.
// NOTE: For Teams agentic identities, streaming is buffered into a single message by the SDK;
// use SendActivityAsync for any messages that must arrive immediately.
await turnContext.SendActivityAsync(MessageFactory.Text("Got it — working on it…"), cancellationToken).ConfigureAwait(false);
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a consistent “multiple discrete messages per turn” UX pattern across the Agent365 sample agents (ack → typing → final response), and documents the approach/limitations for Teams clients.

Changes:

  • Updated Python and Node.js sample message handlers to send an immediate ack and keep a typing indicator alive during LLM work.
  • Updated .NET agent-framework and semantic-kernel samples to document (and in agent-framework, implement) multi-message + typing patterns.
  • Added cross-sample tracking doc and a few sample maintenance tweaks (CrewAI config, platform-specific dependency condition, quickstart fixes, env/launch settings).

Reviewed changes

Copilot reviewed 37 out of 37 changed files in this pull request and generated 14 comments.

Show a summary per file
File Description
python/openai/sample-agent/host_agent_server.py Adds ack + typing-loop around LLM processing.
python/openai/sample-agent/README.md Documents multi-message + typing pattern for Teams.
python/google-adk/sample-agent/hosting.py Adds ack + typing-loop to ADK hosting message handler.
python/google-adk/sample-agent/README.md Documents multi-message + typing pattern for Teams.
python/crewai/sample_agent/src/crew_agent/config/agents.yaml Pins CrewAI agents to an Azure GPT-4o LLM.
python/crewai/sample_agent/pyproject.toml Makes pysqlite3-binary Linux-only.
python/crewai/sample_agent/host_agent_server.py Adds ack + typing-loop around LLM processing (with observability scope).
python/crewai/sample_agent/README.md Documents multi-message + typing pattern for Teams.
python/claude/sample-agent/host_agent_server.py Adds ack + typing-loop around LLM processing.
python/claude/sample-agent/README.md Documents multi-message + typing pattern for Teams.
python/agent-framework/sample-agent/host_agent_server.py Adds ack + typing-loop around LLM processing.
python/agent-framework/sample-agent/README.md Documents multi-message + typing pattern for Teams.
nodejs/vercel-sdk/sample-agent/src/agent.ts Adds ack + typing loop; removes startTypingTimer.
nodejs/vercel-sdk/sample-agent/README.md Documents multi-message + typing pattern for Teams.
nodejs/perplexity/sample-agent/src/agent.ts Adds ack + typing loop to the message activity handler.
nodejs/perplexity/sample-agent/README.md Documents multi-message + typing pattern for Teams.
nodejs/openai/sample-agent/src/agent.ts Adds ack + typing loop; removes startTypingTimer.
nodejs/openai/sample-agent/README.md Documents multi-message + typing pattern for Teams.
nodejs/openai/sample-agent/.env.template Adds HOST binding guidance for Playground.
nodejs/langchain/sample-agent/src/agent.ts Adds ack + typing loop; removes startTypingTimer.
nodejs/langchain/sample-agent/README.md Documents multi-message + typing pattern for Teams.
nodejs/langchain/quickstart-before/src/client.ts Adds Azure OpenAI fallback configuration and renames prompt field.
nodejs/langchain/quickstart-before/src/agent.ts Adds ack + typing loop and InstallationUpdate handler.
nodejs/langchain/quickstart-before/package.json Adds missing TS type dependencies.
nodejs/langchain/quickstart-before/README.md Documents multi-message + typing pattern for Teams.
nodejs/devin/sample-agent/src/agent.ts Adds ack + typing loop in message handling flow.
nodejs/devin/sample-agent/README.md Documents multi-message + typing pattern for Teams.
nodejs/copilot-studio/sample-agent/src/agent.ts Adds ack + typing loop and InstallationUpdate handler.
nodejs/copilot-studio/sample-agent/README.md Documents multi-message + typing pattern for Teams.
nodejs/claude/sample-agent/src/agent.ts Adds ack + typing loop; removes startTypingTimer.
nodejs/claude/sample-agent/README.md Documents multi-message + typing pattern for Teams.
dotnet/semantic-kernel/sample-agent/README.md Documents install/uninstall + multi-message pattern and typing limitations.
dotnet/semantic-kernel/sample-agent/Properties/launchSettings.json Adds SKIP_TOOLING_ON_ERRORS env var for local runs.
dotnet/semantic-kernel/sample-agent/Agents/MyAgent.cs Moves ack/typing earlier and updates auth handler selection logic.
dotnet/agent-framework/sample-agent/README.md Documents multi-message + typing loop pattern for Teams.
dotnet/agent-framework/sample-agent/Agent/MyAgent.cs Implements ack + typing loop + best-effort streaming behavior.
docs/multiple-messages-tracking.md Adds tracking doc for multi-message rollout across samples.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +77 to +78
typingInterval = setInterval(async () => {
await turnContext.sendActivity({ type: 'typing' } as Activity);
Comment on lines +190 to +191
typingInterval = setInterval(async () => {
await turnContext.sendActivity(Activity.fromObject({ type: "typing" }));
Comment on lines 1 to 10
# Copyright (c) Microsoft. All rights reserved.

"""
Generic Agent Host Server
A generic hosting server that can host any agent class that implements the required interface.
"""

import asyncio
import logging
import os
Comment on lines 1 to +6
# Copyright (c) Microsoft. All rights reserved.

"""Generic Agent Host Server - Hosts agents implementing AgentInterface"""

# --- Imports ---
import asyncio
Comment on lines +215 to +216
await context.send_activity(Activity(type="typing"))
await context.send_activity("Got it — working on it…")
Comment on lines 1 to +4
# Copyright (c) Microsoft. All rights reserved.

# --- Imports ---
import asyncio
Comment on lines +52 to +60
OboAuthHandlerName = _configuration.GetValue<string>("AgentApplication:OboAuthHandlerName");
string[] autoSignInHandlersForNotAgenticAuth = !string.IsNullOrEmpty(OboAuthHandlerName) ? [OboAuthHandlerName] : [];

// Register Agentic specific Activity routes. These will only be used if the incoming Activity is Agentic.
this.OnAgentNotification("*", AgentNotificationActivityAsync, RouteRank.Last, autoSignInHandlers: new[] { AgenticIdAuthHandler });
OnActivity(ActivityTypes.InstallationUpdate, OnHireMessageAsync, isAgenticOnly: true, autoSignInHandlers: new[] { AgenticIdAuthHandler });
OnActivity(ActivityTypes.InstallationUpdate, OnHireMessageAsync, isAgenticOnly: false);
OnActivity(ActivityTypes.Message, MessageActivityAsync, rank: RouteRank.Last, isAgenticOnly: true, autoSignInHandlers: new[] { AgenticIdAuthHandler });
OnActivity(ActivityTypes.Message, MessageActivityAsync, rank: RouteRank.Last, isAgenticOnly: false, autoSignInHandlers: autoSignInHandlersForNotAgenticAuth);
Comment on lines +78 to +79
typingInterval = setInterval(async () => {
await turnContext.sendActivity({ type: 'typing' } as Activity);
Comment on lines +70 to +71
typingInterval = setInterval(async () => {
await turnContext.sendActivity({ type: 'typing' } as Activity);
Comment on lines +69 to +73
typingInterval = setInterval(async () => {
await turnContext.sendActivity({ type: 'typing' } as Activity);
}, 4000);
};
const stopTypingLoop = () => { clearInterval(typingInterval); };
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.

2 participants