Skip to main content

Migrating from Teams AI v1

Welcome, fellow agent developer! You've made it through a full major release of Teams AI, and now you want to take the plunge into v2. In this guide, we'll walk you through everything you need to know, from migrating core features like message handlers and auth, to optional AI features like ActionPlanner. We'll also discuss how you can migrate features over incrementally via the botbuilder adapter.

Installing Teams AI v2​

First, let's install Teams AI v2 into your project. Notably, this won't replace any existing installation of Teams AI v1. When you've completed your migration, you can safely remove the @microsoft/teams-ai dependency from your package.json file.

npm install @microsoft/teams.apps

Migrate Application class​

First, migrate your Application class from v1 to the new App class.

import { App } from '@microsoft/teams.apps';
import { LocalStorage } from '@microsoft/teams.common/storage';

// Define app
const app = new App({
clientId: process.env.ENTRA_APP_CLIENT_ID!,
clientSecret: process.env.ENTRA_APP_CLIENT_SECRET!,
tenantId: process.env.ENTRA_TENANT_ID!,
});

// Optionally create local storage
const storage = new LocalStorage();

// Listen for errors
app.event('error', async (client) => {
console.error('Error event received:', client.error);
if (client.activity) {
await app.send(
client.activity.conversation.id,
'An error occurred while processing your message.',
);
}
});

// App creates local server with route for /api/messages
// To reuse your restify or other server,
// create a custom `HttpPlugin`.
(async () => {
// starts the server
await app.start();
})();

Migrate activity handlers​

Both Teams AI v1 and v2 are built atop incoming Activity requests, which trigger handlers in your code when specific type of activities are received. The syntax for how you register different types of Activity handlers differs between the v1 and v2 versions of our SDK.

Message handlers​

// triggers when user sends "/hi" or "@bot /hi"
app.message("/hi", async (context) => {
await context.sendActivity("Hi!");
});
// listen for ANY message to be received
app.activity(
ActivityTypes.Message,
async (context) => {
// echo back users request
await context.sendActivity(
`you said: ${context.activity.text}`
);
}
);

Task modules​

app.on('dialog.open', (client) => {
const dialogType = client.activity.value.data?.opendialogtype;
if (dialogType === 'some-type') {
return {
task: {
type: 'continue',
value: {
title: 'Dialog title',
height: 'medium',
width: 'medium',
url: `https://${process.env.YOUR_WEBSITE_DOMAIN}/some-path`,
fallbackUrl: `https://${process.env.YOUR_WEBSITE_DOMAIN}/fallback-path-for-web`,
completionBotId: process.env.ENTRA_APP_CLIENT_ID!,
},
},
};
}
});

app.on('dialog.submit', async (client) => {
const dialogType = client.activity.value.data?.submissiondialogtype;
if (dialogType === 'some-type') {
const { data } = client.activity.value;
await client.send(JSON.stringify(data));
}
return undefined;
});

Learn more here.

Adaptive cards​

In Teams AI v2, cards have much more rich type validation than existed in v1. However, assuming your cards were valid, it should be easy to migrate to v2.

For existing cards like this, the simplest way to convert that to Teams AI v2 is this:

app.message('/card', async (client) => {
await client.send({
$schema: 'http://adaptivecards.io/schemas/adaptive-card.json',
version: '1.5',
type: 'AdaptiveCard',
body: [
{
type: 'TextBlock',
text: 'Hello, world!',
wrap: true,
isSubtle: false,
},
],
msteams: {
width: 'Full',
},
});
});

Learn more here.

Authentication​

Most agents feature authentication for user identification, interacting with APIs, etc. Whether your Teams AI v1 app used Entra SSO or custom OAuth, porting to v2 should be simple.

const app = new App({
oauth: {
// oauth configurations
/**
* The name of the auth connection to use.
* It should be the same as the OAuth connection name defined in the Azure Bot configuration.
*/
defaultConnectionName: 'graph',
},
logger: new ConsoleLogger('@tests/auth', { level: 'debug' }),
});

app.message('/signout', async (client) => {
if (!client.isSignedIn) return;
await client.signout(); // call signout for your auth connection...
await client.send('you have been signed out!');
});

app.message('/help', async (client) => {
await client.send('your help text');
});

app.on('message', async (client) => {
if (!client.isSignedIn) {
await client.signin({
// Customize the OAuth card text (only renders in OAuth flow, not SSO)
oauthCardText: 'Sign in to your account',
signInButtonText: 'Sign in',
}); // call signin for your auth connection...
return;
}

const me = await client.userGraph.me.get();
log.info(`user "${me.displayName}" already signed in!`);
});

app.event('signin', async (client) => {
const me = await client.userGraph.me.get();
await client.send(`user "${me.displayName}" signed in.`);
await client.send(`Token string length: ${client.token.token.length}`);
});

AI​

Action planner​

When we created Teams AI v1, LLM's didn't natively support tool calling or orchestration. A lot has changed since then, which is why we decided to deprecate ActionPlanner from Teams AI v1, and replace it with something a bit more lightweight. Notably, Teams AI v1 had two similar concepts: functions and actions. In Teams AI v2, these are consolidated into functions.

In Teams AI v2, there is no actions.json file. Instead, function prompts, parameters, etc. are declared in your code.

import '@azure/openai/types';
import { ChatPrompt, Message } from '@microsoft/teams.ai';
import { MessageActivity } from '@microsoft/teams.api';
import { App } from '@microsoft/teams.apps';
import { LocalStorage } from '@microsoft/teams.common/storage';
import { OpenAIChatModel } from '@microsoft/teams.openai';

interface IStorageState {
status: boolean;
messages: Message[];
}

const storage = new LocalStorage<IStorageState>();

const app = new App();

app.on('message', async (client) => {
let state = storage.get(client.activity.from.id);

if (!state) {
state = {
status: false,
messages: [],
};

storage.set(client.activity.from.id, state);
}

const prompt = new ChatPrompt({
messages: state.messages,
instructions: `The following is a conversation with an AI assistant.
The assistant can turn a light on or off.
The lights are currently off.`,
model: new OpenAIChatModel({
model: 'gpt-4o-mini',
apiKey: process.env.OPENAI_API_KEY,
}),
})
.function('get_light_status', 'get the current light status', () => {
return state.status;
})
.function('toggle_lights', 'toggles the lights on/off', () => {
state.status = !state.status;
storage.set(client.activity.from.id, state);
})
.function(
'pause',
'delays for a period of time',
{
type: 'object',
properties: {
time: {
type: 'number',
description: 'the amount of time to delay in milliseconds',
},
},
required: ['time'],
},
async ({ time }: { time: number }) => {
await new Promise((resolve) => setTimeout(resolve, time));
}
);

await prompt.send(client.activity.text, {
onChunk: (chunk) => {
client.stream.emit(new MessageActivity(chunk));
},
});
});

(async () => {
await app.start();
})();

Feedback​

If you supported feedback for AI generated messages, migrating is simple.

import { MessageActivity } from '@microsoft/teams.api';

// Reply with message including feedback buttons
app.on('message', async (client) => {
await client.send(
new MessageActivity('Hey, give me feedback!')
.addAiGenerated() // AI generated label
.addFeedback() // Feedback buttons
);
});

// Listen for feedback submissions
app.on('message.submit.feedback', async ({ activity, log }) => {
// custom logic here...
});

Note: In Teams AI v2, you do not need to opt into feedback at the App level.

You can learn more about feedback in Teams AI v2 here.

Incrementally migrating code via botbuilder plugin​

info

Comparison code coming soon!

If you aren't ready to migrate all of your code, you can run your existing Teams AI v1 code in parallel with Teams AI v2. Learn more here.