Share via


Azure Bot Framework SDK to Microsoft 365 Agents SDK migration guidance for nodejs

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:

Bot Framework static method Agents SDK instance method
TurnContext.applyConversationReference activity.applyConversationReference
TurnContext.getConversationReference activity.getConversationReference
TurnContext.getReplyConversationReference activity.getReplyConversationReference
TurnContext.removeRecipientMention activity.removeRecipientMention
TurnContext.getMentions activity.getMentions
TurnContext.removeMentionText activity.removeMentionText

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()