Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions nodejs/semantic-kernel/sample-agent/.env.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# OpenAI Configuration
# Use EITHER standard OpenAI OR Azure OpenAI (not both)

# Option 1: Standard OpenAI API
OPENAI_API_KEY=
OPENAI_MODEL=gpt-4o

# Option 2: Azure OpenAI (takes precedence if AZURE_OPENAI_API_KEY is set)
AZURE_OPENAI_API_KEY=
AZURE_OPENAI_ENDPOINT=
AZURE_OPENAI_DEPLOYMENT=
AZURE_OPENAI_API_VERSION=2024-10-21

# MCP Tooling Configuration
BEARER_TOKEN=

# Enable to use observability exporter, default is false which means using console exporter
ENABLE_A365_OBSERVABILITY_EXPORTER=false
# Used by the sample to demo using custom token resolver and token cache when it is true, otherwise use the built-in AgenticTokenCache
Use_Custom_Resolver=true
# optional - set to enable observability logs, value can be 'info', 'warn', or 'error', default to 'none' if not set
A365_OBSERVABILITY_LOG_LEVEL=

# Environment Settings
NODE_ENV=development
HOST=127.0.0.1

# Telemetry and Tracing Configuration
DEBUG=agents:*

# Skip tooling errors in development (graceful fallback to bare LLM mode)
SKIP_TOOLING_ON_ERRORS=true

# Use Agentic Authentication rather than OBO
USE_AGENTIC_AUTH=false

# Service Connection Settings
connections__service_connection__settings__clientId=
connections__service_connection__settings__clientSecret=
connections__service_connection__settings__tenantId=

# Set service connection as default
connectionsMap__0__serviceUrl=*
connectionsMap__0__connection=service_connection

# AgenticAuthentication Options
agentic_type=agentic
agentic_altBlueprintConnectionName=service_connection
agentic_scopes=ea9ffc3e-8a23-4a7d-836d-234d7c7565c1/.default
35 changes: 35 additions & 0 deletions nodejs/semantic-kernel/sample-agent/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# TeamsFx files
env/.env.*.user
env/.env.local
env/.env.sandbox
.localConfigs
.localConfigs.playground
.notification.localstore.json
.notification.playgroundstore.json
appPackage/build

# dependencies
node_modules/

# misc
.env
.deployment
.DS_Store

# build
dist/
publish/

# Dev tool directories
/devTools/

# A365 configuration (generated / user-specific)
a365.config.json
a365.generated.config.json

# Packaged artifacts
app.zip
manifest/

# TypeScript intermediates
*.tsbuildinfo
219 changes: 219 additions & 0 deletions nodejs/semantic-kernel/sample-agent/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
# Semantic Kernel Sample Agent - Node.js

This sample demonstrates how to build an agent using Semantic Kernel patterns in Node.js with the Microsoft Agent 365 SDK. It covers:

- **Observability**: End-to-end tracing, caching, and monitoring for agent applications
- **Notifications**: Services and models for managing user notifications
- **Tools**: Model Context Protocol tools for building advanced agent solutions
- **Hosting Patterns**: Hosting with Microsoft 365 Agents SDK
- **Function Calling**: Semantic Kernel-style automatic function calling with plugins

This sample uses the [Microsoft Agent 365 SDK for Node.js](https://github.com/microsoft/Agent365-nodejs).

For comprehensive documentation and guidance on building agents with the Microsoft Agent 365 SDK, including how to add tooling, observability, and notifications, visit the [Microsoft Agent 365 Developer Documentation](https://learn.microsoft.com/en-us/microsoft-agent-365/developer/).

## Demonstrates

This sample mirrors the [C#/.NET Semantic Kernel sample](../../../dotnet/semantic-kernel/sample-agent/) and demonstrates:

- **Semantic Kernel Agent Pattern**: Uses OpenAI Chat Completions with function calling (tools) in a loop — equivalent to the C# `ChatCompletionAgent` with `FunctionChoiceBehavior.Auto`
- **Plugin System**: Local plugins for terms and conditions management, similar to the C# `KernelPlugin` pattern
- **MCP Tool Integration**: Dynamic tool loading from Agent 365 MCP servers
- **Azure OpenAI / OpenAI Support**: Configurable to use either Azure OpenAI or standard OpenAI
- **Observability**: Agent 365 tracing and telemetry with baggage propagation
- **Notifications**: Email notification handling
- **Terms and Conditions Flow**: Plugin-based T&C acceptance workflow

## Prerequisites

- Node.js 18.x or higher
- Microsoft Agent 365 SDK
- Azure/OpenAI API credentials

## Configuration

1. Copy `.env.template` to `.env`:
```bash
cp .env.template .env
```

2. Configure your LLM provider in `.env`:

**Option 1: Standard OpenAI**
```env
OPENAI_API_KEY=<<YOUR_API_KEY>>
OPENAI_MODEL=gpt-4o
```

**Option 2: Azure OpenAI**
```env
AZURE_OPENAI_API_KEY=<<YOUR_API_KEY>>
AZURE_OPENAI_ENDPOINT=<<YOUR_ENDPOINT>>
AZURE_OPENAI_DEPLOYMENT=<<YOUR_DEPLOYMENT_NAME>>
AZURE_OPENAI_API_VERSION=2024-10-21
```

3. For MCP tooling (optional), set the bearer token:
```bash
a365 develop get-token
```
Copy the token value to `BEARER_TOKEN` in your `.env` file.

## Working with User Identity

On every incoming message, the A365 platform populates `activity.from` with basic user information — always available with no API calls or token acquisition:

| Field | Description |
|---|---|
| `activity.from.id` | Channel-specific user ID (e.g., `29:1AbcXyz...` in Teams) |
| `activity.from.name` | Display name as known to the channel |
| `activity.from.aadObjectId` | Azure AD Object ID — use this to call Microsoft Graph |

The sample logs these fields at the start of every message turn and injects the display name into the LLM system instructions for personalized responses.

## Handling Agent Install and Uninstall

When a user installs (hires) or uninstalls (removes) the agent, the A365 platform sends an `InstallationUpdate` activity. The sample handles this in `handleInstallationUpdateActivity` ([src/agent.ts](src/agent.ts)):

| Action | Description |
|---|---|
| `add` | Agent was installed — send a welcome message |
| `remove` | Agent was uninstalled — send a farewell message |

```typescript
if (context.activity.action === 'add') {
setTermsAndConditionsAccepted(true);
await context.sendActivity('Thank you for hiring me! Looking forward to assisting you in your professional journey!');
} else if (context.activity.action === 'remove') {
setTermsAndConditionsAccepted(false);
await context.sendActivity('Thank you for your time, I enjoyed working with you.');
}
```

To test with Agents Playground, use **Mock an Activity → Install application** to send a simulated `installationUpdate` activity.

## Sending Multiple Messages in Teams

Agent365 agents can send multiple discrete messages in response to a single user prompt in Teams. This is achieved by calling `sendActivity` multiple times within a single turn.

> **Important**: Streaming responses are not supported for agentic identities in Teams. The SDK detects agentic identity and buffers the stream into a single message. Use `sendActivity` directly to send immediate, discrete messages to the user.

The sample demonstrates this in `handleAgentMessageActivity` ([src/agent.ts](src/agent.ts)):

```typescript
// Message 1: immediate ack — reaches the user right away
await turnContext.sendActivity('Got it — working on it…');

// ... LLM processing ...

// Message 2: the LLM response
await turnContext.sendActivity(response.content);
```

### Typing Indicators

The agent sends typing indicators in a loop every ~4 seconds to keep the `...` animation alive while the LLM processes the request:

```typescript
let typingInterval: ReturnType<typeof setInterval> | undefined;
const startTypingLoop = () => {
typingInterval = setInterval(() => {
turnContext.sendActivity({ type: 'typing' } as Activity).catch(() => {});
}, 4000);
};
const stopTypingLoop = () => { clearInterval(typingInterval); };
```

> **Note**: Typing indicators are only visible in 1:1 chats and small group chats — not in channels.

## How to Run This Sample

### 1. Install Dependencies

```bash
npm install
```

### 2. Build

```bash
npm run build
```

### 3. Run

**Production:**
```bash
npm start
```

**Development (with hot reload):**
```bash
npm run dev
```

### 4. Test with Agents Playground

```bash
npm run test-tool
```

## Project Structure

```
src/
├── index.ts # Express server entry point
├── agent.ts # MyAgent class — message routing, notifications, install/uninstall
├── client.ts # Semantic Kernel-style agent with function calling loop
├── plugins.ts # Terms and conditions plugins (accept/reject)
├── openai-config.ts # OpenAI/Azure OpenAI client configuration
└── token-cache.ts # In-memory token cache for observability
```

## Troubleshooting

| Issue | Solution |
|---|---|
| `No OpenAI credentials configured` | Set `OPENAI_API_KEY` or `AZURE_OPENAI_*` variables in `.env` |
| `Failed to register MCP tool servers` | Ensure `BEARER_TOKEN` is set. Run `a365 develop get-token` to get a fresh token |
| `Token expired` | Bearer tokens expire regularly. Refresh with `a365 develop get-token` |
| Agent not responding | Check that `NODE_ENV=development` is set in `.env` for local testing |
| `ECONNREFUSED` on port 3978 | Another process may be using port 3978. Change `PORT` in `.env` |

## Running the Agent

To set up and test this agent, refer to the [Configure Agent Testing](https://learn.microsoft.com/en-us/microsoft-agent-365/developer/testing?tabs=nodejs) guide for complete instructions.

## Support

For issues, questions, or feedback:

- **Issues**: Please file issues in the [GitHub Issues](https://github.com/microsoft/Agent365-nodejs/issues) section
- **Documentation**: See the [Microsoft Agents 365 Developer documentation](https://learn.microsoft.com/en-us/microsoft-agent-365/developer/)
- **Security**: For security issues, please see [SECURITY.md](../../../SECURITY.md)

## Contributing

This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit <https://cla.opensource.microsoft.com>.

When you submit a pull request, a CLA bot will automatically determine whether you need to provide a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repos using our CLA.

This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.

## Additional Resources

- [Microsoft Agent 365 SDK - Node.js repository](https://github.com/microsoft/Agent365-nodejs)
- [Microsoft 365 Agents SDK - Node.js repository](https://github.com/Microsoft/Agents-for-js)
- [Semantic Kernel documentation](https://learn.microsoft.com/semantic-kernel/)
- [OpenAI API documentation](https://platform.openai.com/docs/)
- [Node.js API documentation](https://learn.microsoft.com/javascript/api/?view=m365-agents-sdk&preserve-view=true)

## Trademarks

*Microsoft, Windows, Microsoft Azure and/or other Microsoft products and services referenced in the documentation may be either trademarks or registered trademarks of Microsoft in the United States and/or other countries. The licenses for this project do not grant you rights to use any Microsoft names, logos, or trademarks. Microsoft's general trademark guidelines can be found at http://go.microsoft.com/fwlink/?LinkID=254653.*

## License

Copyright (c) Microsoft Corporation. All rights reserved.

Licensed under the MIT License - see the [LICENSE](../../../LICENSE.md) file for details.
11 changes: 11 additions & 0 deletions nodejs/semantic-kernel/sample-agent/ToolingManifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"mcpServers": [
{
"mcpServerName": "mcp_MailTools",
"mcpServerUniqueName": "mcp_MailTools",
"url": "https://agent365.svc.cloud.microsoft/agents/servers/mcp_MailTools",
"scope": "McpServers.Mail.All",
"audience": "ea9ffc3e-8a23-4a7d-836d-234d7c7565c1"
}
]
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
50 changes: 50 additions & 0 deletions nodejs/semantic-kernel/sample-agent/appManifest/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/teams/v1.22/MicrosoftTeams.schema.json",
"manifestVersion": "1.22",
"version": "1.0.0",
"id": "${{AAD_APP_CLIENT_ID}}",
"developer": {
"name": "Microsoft, Inc.",
"websiteUrl": "https://example.azurewebsites.net",
"privacyUrl": "https://example.azurewebsites.net/privacy",
"termsOfUseUrl": "https://example.azurewebsites.net/termsofuse"
},
"icons": {
"color": "color.png",
"outline": "outline.png"
},
"name": {
"short": "Agent 365 SK Sample Agent (Node.js)",
"full": "Agent 365 SDK Semantic Kernel Sample Agent (Node.js)"
},
"description": {
"short": "Sample demonstrating Agent 365 SDK, Teams, and Semantic Kernel in Node.js",
"full": "Sample demonstrating Agent 365 SDK, Teams, and Semantic Kernel in Node.js"
},
"accentColor": "#FFFFFF",
"copilotAgents": {
"customEngineAgents": [
{
"id": "${{AAD_APP_CLIENT_ID}}",
"type": "bot"
}
]
},
"bots": [
{
"botId": "${{AAD_APP_CLIENT_ID}}",
"scopes": [
"personal"
],
"supportsFiles": false,
"isNotificationOnly": false
}
],
"permissions": [
"identity",
"messageTeamMembers"
],
"validDomains": [
"<<BOT_DOMAIN>>"
]
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading