Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
This article describes the required changes to migrate from Bot Framework SDK for Node.js.
Prerequisites
- Node.js version 20 or higher
- Existing Bot Framework SDK project
- Azure Bot Service resource (remains unchanged during migration)
NodeJS SDK Code changes
The changes in this section are due to differences between the Azure Bot Framework SDK and the Microsoft 365 Agents SDK JavaScript .
Azure resources
Your Azure resources remain unchanged. You need to reference your appsettings properties for MicrosoftAppType, MicrosoftAppId, MicrosoftAppPassword, and MicrosoftAppTenantId. However those setting names are no longer used and can be deleted later. Learn more about environment configuration
First steps
Apply the following changes first to address most differences. You'll still need to debug and check for other differences after you apply these changes.
Update package dependencies
This change doesn't get all required namespaces settled, but it covers the bulk of them.
| Scenario | Bot Framework SDK | Agents SDK |
|---|---|---|
| Core hosting | botbuilder |
@microsoft/agents-hosting |
| Activity schema | botframework-schema |
@microsoft/agents-activity |
| Dialogs | botbuilder-dialogs |
@microsoft/agents-hosting-dialogs |
| Azure Cosmos DB | botbuilder-azure |
@microsoft/agents-hosting-storage-cosmos |
| Azure Blob Storage | botbuilder-azure-blobs |
@microsoft/agents-hosting-storage-blob |
| Express server utilities | Manual setup | @microsoft/agents-hosting-express |
Bots using Teams
If your bot uses Teams, add a package dependency for @microsoft/agents-hosting-extensions-teams
Update imports/require
Use find and replace to make the following changes:
| Bot Framework | Agents SDK |
|---|---|
require('botframework-schema'); |
require('@microsoft/agents-activity') |
require('botbuilder'); |
require('@microsoft/agents-hosting') |
require('botbuilder-dialogs'); |
require('@microsoft/agents-hosting-dialogs') |
Agents SDK Activity class
The @microsoft/agents-activity package includes the Activity class to perform parsing based on zod. You can parse and validate your custom activities from JSON with Activity.fromJson() or from literal JavaScript objects with Activity.fromObject().
Additionally, the Activity class centralizes all operations related to activity payload, such as getConversationReference. The methods in the following table moved from TurnContext and now operate over the current activity instance:
Startup and Configuration
The Agents SDK configuration system replaces the ConfigurationBotFrameworkAuthentication class with the AuthConfiguration interface.
To load the configuration from the default .env file uses loadAuthConfigFromEnv.
Important
The configuration variables are described in Configure authentication in JavaScript.
Environment Configuration
Create a .env file with the following variables:
# Required for Azure Bot Service
clientId=your-app-id
clientSecret=your-app-secret
tenantId=your-tenant-id
# Optional - for local debugging
PORT=3978
DEBUG=true
Migration Note: Update your environment variable names as shown in the following table:
| Bot Framework SDK | Agents SDK |
|---|---|
MicrosoftAppId |
clientId |
MicrosoftAppPassword |
clientSecret |
MicrosoftAppTenantId |
tenantId |
Authentication and Security
When Bot Framework SDK authorizes incoming requests, it includes JSON Web Token (JWT) authorization tokens in the stack. Agents SDK doesn't. When using a web server runtime such as express you have to configure a JWT middleware to authorize incoming requests based on the JWT Bearer token, the Agents SDK provides the authorizeJWT(AuthConfiguration) method.
JWT Middleware (Required for production):
import { authorizeJWT, loadAuthConfigFromEnv } from '@microsoft/agents-hosting'
const authConfig = loadAuthConfigFromEnv()
server.use(authorizeJWT(authConfig))
Local Development:
For local debugging, JWT validation can be disabled:
// Only for local development - NEVER in production
if (process.env.NODE_ENV === 'development') {
// JWT validation disabled for local testing
} else {
server.use(authorizeJWT(authConfig))
}
Server Setup Options
The Agents SDK provides two approaches for setting up your server:
Use startServer method
Use this simplified approach for new projects when you want a minimal setup and don't need custom middleware.
Update your initialization code from botbuilder:
const { EchoBot } = require('./bot');
const {
CloudAdapter,
ConfigurationBotFrameworkAuthentication
} = require('botbuilder');
const botFrameworkAuthentication = new ConfigurationBotFrameworkAuthentication(process.env);
const adapter = new CloudAdapter(botFrameworkAuthentication);
const myBot = new EchoBot();
const server = express();
server.use(express.json());
server.post('/api/messages', async (req, res) =>
await adapter.process(req, res, (context) =>
myBot.run(context));
);
To agents-hosting with startServer():
const { EchoBot } = require('./bot');
const { startServer } = require('@microsoft/agents-hosting-express')
startServer(new EchoBot());
Manual Express setup
Use this approach when migrating existing bots, need custom middleware, want full control over Express configuration
const { EchoBot } = require('./bot');
const {
CloudAdapter,
loadAuthConfigFromEnv, // Update
authorizeJWT // Update
} = require('@microsoft/agents-hosting'); // Update
const authConfig = loadAuthConfigFromEnv(); // Update
const adapter = new CloudAdapter(authConfig); // Update
const myBot = new EchoBot();
const server = express();
server.use(express.json());
server.use(authorizeJWT(authConfig)); // Update
server.post('/api/messages', async (req, res) =>
await adapter.process(req, res, (context) =>
myBot.run(context));
);
const port = process.env.PORT || 3978;
server.listen(port, () => {
console.log(`Server listening on port ${port}`);
}).on('error', (err) => {
console.error('Server failed to start:', err);
process.exit(1);
});
ActivityHandler support
Most bots created with the Bot Framework SDK are based on the botbuilder-core.ActivityHandler base class.
The Agents SDK provides a compatible agents-hosting.ActivityHandler that maintains the same API surface for easier migration.
Key differences
Some key differences between the Bot Framework SDK and the Agents SDK include:
Handler Parameters:
| SDK | Handler Type |
|---|---|
| Bot Framework SDK | BotHandler |
| Agents SDK | AgentHandler |
Additional Methods in Agents SDK:
| Method | Description |
|---|---|
onMessageDelete |
Handles message deletion activities |
onMessageUpdate |
Handles message update activities |
onSignInInvoke |
Handles sign-in invoke activities |
Missing Methods in Agents SDK:
| Method | Reason |
|---|---|
onCommand |
Command activities aren't supported |
onCommandResult |
Command result activities aren't supported |
onEvent |
Generic event handling (specific event types like onTokenResponseEvent are still supported) |
onTokenResponseEvent |
OAuth token response events |
Method Signature Changes:
All handler methods return ActivityHandler instead of this for method chaining. Handler functions use the AgentHandler type, which has the same signature as BotHandler
Migration Example:
Bot Framework SDK:
const { ActivityHandler } = require('botbuilder');
class MyBot extends ActivityHandler {
constructor() {
super();
this.onMessage(async (context, next) => {
await context.sendActivity('Hello!');
await next();
});
}
}
The migration is mostly straightforward, with the main change being the import statement and handler type. Most existing ActivityHandler-based bots should work with minimal modifications.
Important
The ActivityHandler is deprecated in favor of the new AgentApplication class
Migrating from ActivityHandler to AgentApplication
While ActivityHandler is supported for backward compatibility, the recommended approach is to use AgentApplication:
Using ActivityHandler:
import { ActivityHandler } from '@microsoft/agents-hosting'
class MyBot extends ActivityHandler {
constructor() {
super()
this.onMessage(async (context, next) => {
await context.sendActivity('Hello!')
await next()
})
this.onMembersAdded(async (context, next) => {
await context.sendActivity('Welcome!')
await next()
})
}
}
Key Differences
The following table describes key feature differences between ActivityHandler and AgentApplication:
| Feature | ActivityHandler |
AgentApplication |
|---|---|---|
| State Management | Manual state management required | Built-in state management provided |
| Event Handling | Generic event handlers (for example, onMembersAdded) |
More specific event handlers (for example, membersAdded) |
| Next Function | Handlers require calling next() |
Handlers don't require calling next() |
| Storage | Manual storage configuration | Built-in storage support with automatic state persistence |
Common Migration Patterns
Some common migration patterns are:
Simple Echo Bot
Bot Framework
const { ActivityHandler } = require('botbuilder')
class EchoBot extends ActivityHandler {
constructor() {
super()
this.onMessage(async (context, next) => {
await context.sendActivity(`You said: ${context.activity.text}`)
await next()
})
}
}
State Management
Using AgentApplication:
import { AgentApplication, MemoryStorage } from '@microsoft/agents-hosting'
const agent = new AgentApplication({
storage: new MemoryStorage()
})
agent.onMessage('/count', async (context, state) => {
const count = state.conversation.count ?? 0
state.conversation.count = count + 1
await context.sendActivity(`Count: ${state.conversation.count}`)
})
Inheriting from AgentApplication
For more complex scenarios, you can create a class that inherits from AgentApplication:
import { AgentApplication, MemoryStorage, MessageFactory } from '@microsoft/agents-hosting'
class MyAgent extends AgentApplication {
constructor() {
super({
storage: new MemoryStorage()
})
this.setupRoutes()
}
setupRoutes() {
this.onMessage('/help', this.handleHelp)
this.onMessage('/status', this.handleStatus)
this.onMessage('/reset', this.handleReset)
this.onActivity('message', this.handleDefault)
this.onConversationUpdate('membersAdded', this.handleWelcome)
}
handleHelp = async (context, state) => {
const helpText = `
Available commands:
- /help - Show this help message
- /status - Show current status
- /reset - Reset conversation state
`
await context.sendActivity(MessageFactory.text(helpText))
}
handleStatus = async (context, state) => {
const messageCount = state.conversation.messageCount ?? 0
await context.sendActivity(`Messages processed: ${messageCount}`)
}
handleReset = async (context, state) => {
state.deleteConversationState()
await context.sendActivity('Conversation state has been reset.')
}
handleWelcome = async (context, state) => {
const welcomeText = 'Welcome! Type /help to see available commands.'
await context.sendActivity(MessageFactory.text(welcomeText))
}
handleDefault = async (context, state) => {
// Increment message counter
const messageCount = (state.conversation.messageCount ?? 0) + 1
state.conversation.messageCount = messageCount
const replyText = `Echo: ${context.activity.text} (Message #${messageCount})`
await context.sendActivity(MessageFactory.text(replyText))
}
}
export default new MyAgent()
Benefits of this pattern
| Benefit | Description |
|---|---|
| Better organization | Separate methods for different handlers |
| Reusability | Can be easily extended or inherited further |
| Testability | Individual methods can be unit tested |
| Maintainability | Cleaner code structure for complex bots |
| Arrow functions | Automatically bind this context without needing .bind() |