@chat-adapter/teams 4.0.2 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { CardElement, BaseFormatConverter, PostableMessage, Root, Adapter, ChatInstance, WebhookOptions, RawMessage, EmojiValue, FetchOptions, Message, ThreadInfo, FormattedContent } from 'chat';
1
+ import { CardElement, BaseFormatConverter, AdapterPostableMessage, Root, Adapter, ChatInstance, WebhookOptions, RawMessage, EmojiValue, FetchOptions, FetchResult, ThreadInfo, Message, FormattedContent } from 'chat';
2
2
 
3
3
  /**
4
4
  * Teams Adaptive Card converter for cross-platform cards.
@@ -56,7 +56,7 @@ declare class TeamsFormatConverter extends BaseFormatConverter {
56
56
  /**
57
57
  * Override renderPostable to convert @mentions in plain strings.
58
58
  */
59
- renderPostable(message: PostableMessage): string;
59
+ renderPostable(message: AdapterPostableMessage): string;
60
60
  /**
61
61
  * Render an AST to Teams format.
62
62
  * Teams accepts standard markdown, so we just stringify cleanly.
@@ -93,6 +93,7 @@ declare class TeamsAdapter implements Adapter<TeamsThreadId, unknown> {
93
93
  readonly userName: string;
94
94
  readonly botUserId?: string;
95
95
  private botAdapter;
96
+ private graphClient;
96
97
  private chat;
97
98
  private logger;
98
99
  private formatConverter;
@@ -125,13 +126,13 @@ declare class TeamsAdapter implements Adapter<TeamsThreadId, unknown> {
125
126
  */
126
127
  private createAttachment;
127
128
  private normalizeMentions;
128
- postMessage(threadId: string, message: PostableMessage): Promise<RawMessage<unknown>>;
129
+ postMessage(threadId: string, message: AdapterPostableMessage): Promise<RawMessage<unknown>>;
129
130
  /**
130
- * Extract card element from a PostableMessage if present.
131
+ * Extract card element from a AdapterPostableMessage if present.
131
132
  */
132
133
  private extractCard;
133
134
  /**
134
- * Extract files from a PostableMessage if present.
135
+ * Extract files from a AdapterPostableMessage if present.
135
136
  */
136
137
  private extractFiles;
137
138
  /**
@@ -139,7 +140,7 @@ declare class TeamsAdapter implements Adapter<TeamsThreadId, unknown> {
139
140
  * Uses inline data URIs for small files.
140
141
  */
141
142
  private filesToAttachments;
142
- editMessage(threadId: string, messageId: string, message: PostableMessage): Promise<RawMessage<unknown>>;
143
+ editMessage(threadId: string, messageId: string, message: AdapterPostableMessage): Promise<RawMessage<unknown>>;
143
144
  deleteMessage(threadId: string, messageId: string): Promise<void>;
144
145
  addReaction(_threadId: string, _messageId: string, _emoji: EmojiValue | string): Promise<void>;
145
146
  removeReaction(_threadId: string, _messageId: string, _emoji: EmojiValue | string): Promise<void>;
@@ -152,7 +153,26 @@ declare class TeamsAdapter implements Adapter<TeamsThreadId, unknown> {
152
153
  * If no cached values are found, defaults are used (which may not work for all tenants).
153
154
  */
154
155
  openDM(userId: string): Promise<string>;
155
- fetchMessages(_threadId: string, _options?: FetchOptions): Promise<Message<unknown>[]>;
156
+ fetchMessages(threadId: string, options?: FetchOptions): Promise<FetchResult<unknown>>;
157
+ /**
158
+ * Fetch messages from a Teams channel thread using the channel-specific Graph API endpoint.
159
+ * This provides proper thread-level filtering by fetching only replies to a specific message.
160
+ *
161
+ * Endpoint: GET /teams/{team-id}/channels/{channel-id}/messages/{message-id}/replies
162
+ */
163
+ private fetchChannelThreadMessages;
164
+ /**
165
+ * Extract plain text from a Graph API message.
166
+ */
167
+ private extractTextFromGraphMessage;
168
+ /**
169
+ * Extract a title/summary from an Adaptive Card structure.
170
+ */
171
+ private extractCardTitle;
172
+ /**
173
+ * Extract attachments from a Graph API message.
174
+ */
175
+ private extractAttachmentsFromGraphMessage;
156
176
  fetchThread(threadId: string): Promise<ThreadInfo>;
157
177
  encodeThreadId(platformData: TeamsThreadId): string;
158
178
  /**
package/dist/index.js CHANGED
@@ -1,8 +1,14 @@
1
1
  // src/index.ts
2
+ import { ClientSecretCredential } from "@azure/identity";
3
+ import { Client } from "@microsoft/microsoft-graph-client";
4
+ import {
5
+ TokenCredentialAuthenticationProvider
6
+ } from "@microsoft/microsoft-graph-client/authProviders/azureTokenCredentials";
2
7
  import {
3
8
  ActivityTypes,
4
9
  CloudAdapter,
5
- ConfigurationBotFrameworkAuthentication
10
+ ConfigurationBotFrameworkAuthentication,
11
+ TeamsInfo
6
12
  } from "botbuilder";
7
13
  import {
8
14
  convertEmojiPlaceholders as convertEmojiPlaceholders2,
@@ -321,6 +327,7 @@ var TeamsAdapter = class {
321
327
  userName;
322
328
  botUserId;
323
329
  botAdapter;
330
+ graphClient = null;
324
331
  chat = null;
325
332
  logger = null;
326
333
  formatConverter = new TeamsFormatConverter();
@@ -338,6 +345,20 @@ var TeamsAdapter = class {
338
345
  MicrosoftAppTenantId: config.appType === "SingleTenant" ? config.appTenantId : void 0
339
346
  });
340
347
  this.botAdapter = new ServerlessCloudAdapter(auth);
348
+ if (config.appTenantId) {
349
+ const credential = new ClientSecretCredential(
350
+ config.appTenantId,
351
+ config.appId,
352
+ config.appPassword
353
+ );
354
+ const authProvider = new TokenCredentialAuthenticationProvider(
355
+ credential,
356
+ {
357
+ scopes: ["https://graph.microsoft.com/.default"]
358
+ }
359
+ );
360
+ this.graphClient = Client.initWithMiddleware({ authProvider });
361
+ }
341
362
  }
342
363
  async initialize(chat) {
343
364
  this.chat = chat;
@@ -382,12 +403,82 @@ var TeamsAdapter = class {
382
403
  const activity = context.activity;
383
404
  if (activity.from?.id && activity.serviceUrl) {
384
405
  const userId = activity.from.id;
385
- const tenantId = activity.channelData?.tenant?.id;
406
+ const channelData = activity.channelData;
407
+ const tenantId = channelData?.tenant?.id;
386
408
  const ttl = 30 * 24 * 60 * 60 * 1e3;
387
409
  this.chat.getState().set(`teams:serviceUrl:${userId}`, activity.serviceUrl, ttl);
388
410
  if (tenantId) {
389
411
  this.chat.getState().set(`teams:tenantId:${userId}`, tenantId, ttl);
390
412
  }
413
+ const team = channelData?.team;
414
+ const teamAadGroupId = team?.aadGroupId;
415
+ const teamThreadId = team?.id;
416
+ const conversationId = activity.conversation?.id || "";
417
+ const baseChannelId = conversationId.replace(/;messageid=\d+/, "");
418
+ if (teamAadGroupId && channelData?.channel?.id && tenantId) {
419
+ const context2 = {
420
+ teamId: teamAadGroupId,
421
+ // Use aadGroupId (GUID) for Graph API
422
+ channelId: channelData.channel.id,
423
+ tenantId
424
+ };
425
+ const contextJson = JSON.stringify(context2);
426
+ this.chat.getState().set(`teams:channelContext:${baseChannelId}`, contextJson, ttl);
427
+ if (teamThreadId) {
428
+ this.chat.getState().set(`teams:teamContext:${teamThreadId}`, contextJson, ttl);
429
+ }
430
+ this.logger?.info(
431
+ "Cached Teams team GUID from installation/update event",
432
+ {
433
+ activityType: activity.type,
434
+ conversationId: baseChannelId,
435
+ teamThreadId,
436
+ teamGuid: context2.teamId,
437
+ channelId: context2.channelId
438
+ }
439
+ );
440
+ } else if (teamThreadId && channelData?.channel?.id && tenantId) {
441
+ const cachedTeamContext = await this.chat.getState().get(`teams:teamContext:${teamThreadId}`);
442
+ if (cachedTeamContext) {
443
+ this.chat.getState().set(
444
+ `teams:channelContext:${baseChannelId}`,
445
+ cachedTeamContext,
446
+ ttl
447
+ );
448
+ this.logger?.info("Using cached Teams team GUID for channel", {
449
+ conversationId: baseChannelId,
450
+ teamThreadId
451
+ });
452
+ } else {
453
+ try {
454
+ const teamDetails = await TeamsInfo.getTeamDetails(context);
455
+ if (teamDetails?.aadGroupId) {
456
+ const fetchedContext = {
457
+ teamId: teamDetails.aadGroupId,
458
+ channelId: channelData.channel.id,
459
+ tenantId
460
+ };
461
+ const contextJson = JSON.stringify(fetchedContext);
462
+ this.chat.getState().set(`teams:channelContext:${baseChannelId}`, contextJson, ttl);
463
+ this.chat.getState().set(`teams:teamContext:${teamThreadId}`, contextJson, ttl);
464
+ this.logger?.info(
465
+ "Fetched and cached Teams team GUID via TeamsInfo API",
466
+ {
467
+ conversationId: baseChannelId,
468
+ teamThreadId,
469
+ teamGuid: teamDetails.aadGroupId,
470
+ teamName: teamDetails.name
471
+ }
472
+ );
473
+ }
474
+ } catch (error) {
475
+ this.logger?.debug(
476
+ "Could not fetch team details (may not be a team scope)",
477
+ { teamThreadId, error }
478
+ );
479
+ }
480
+ }
481
+ }
391
482
  }
392
483
  if (activity.type === ActivityTypes.MessageReaction) {
393
484
  this.handleReactionActivity(activity, options);
@@ -596,7 +687,13 @@ var TeamsAdapter = class {
596
687
  edited: false
597
688
  },
598
689
  attachments: (activity.attachments || []).filter(
599
- (att) => att.contentType !== "application/vnd.microsoft.card.adaptive"
690
+ (att) => (
691
+ // Filter out adaptive cards (handled separately as cards, not attachments)
692
+ att.contentType !== "application/vnd.microsoft.card.adaptive" && // Filter out text/html without contentUrl - this is just the formatted
693
+ // version of the message text, not an actual file attachment.
694
+ // Real HTML file attachments would have a contentUrl.
695
+ !(att.contentType === "text/html" && !att.contentUrl)
696
+ )
600
697
  ).map((att) => this.createAttachment(att))
601
698
  };
602
699
  }
@@ -697,7 +794,7 @@ var TeamsAdapter = class {
697
794
  };
698
795
  }
699
796
  /**
700
- * Extract card element from a PostableMessage if present.
797
+ * Extract card element from a AdapterPostableMessage if present.
701
798
  */
702
799
  extractCard(message) {
703
800
  if (isCardElement(message)) {
@@ -709,7 +806,7 @@ var TeamsAdapter = class {
709
806
  return null;
710
807
  }
711
808
  /**
712
- * Extract files from a PostableMessage if present.
809
+ * Extract files from a AdapterPostableMessage if present.
713
810
  */
714
811
  extractFiles(message) {
715
812
  if (typeof message === "object" && message !== null && "files" in message) {
@@ -911,11 +1008,345 @@ var TeamsAdapter = class {
911
1008
  serviceUrl
912
1009
  });
913
1010
  }
914
- async fetchMessages(_threadId, _options = {}) {
915
- throw new NotImplementedError(
916
- "Teams does not provide a bot API to fetch message history. Use Microsoft Graph API instead.",
917
- "fetchMessages"
918
- );
1011
+ async fetchMessages(threadId, options = {}) {
1012
+ if (!this.graphClient) {
1013
+ throw new NotImplementedError(
1014
+ "Teams fetchMessages requires appTenantId to be configured for Microsoft Graph API access.",
1015
+ "fetchMessages"
1016
+ );
1017
+ }
1018
+ const { conversationId } = this.decodeThreadId(threadId);
1019
+ const limit = options.limit || 50;
1020
+ const cursor = options.cursor;
1021
+ const direction = options.direction ?? "backward";
1022
+ const messageIdMatch = conversationId.match(/;messageid=(\d+)/);
1023
+ const threadMessageId = messageIdMatch?.[1];
1024
+ const baseConversationId = conversationId.replace(/;messageid=\d+/, "");
1025
+ let channelContext = null;
1026
+ if (threadMessageId && this.chat) {
1027
+ const cachedContext = await this.chat.getState().get(`teams:channelContext:${baseConversationId}`);
1028
+ if (cachedContext) {
1029
+ try {
1030
+ channelContext = JSON.parse(cachedContext);
1031
+ } catch {
1032
+ }
1033
+ }
1034
+ }
1035
+ try {
1036
+ this.logger?.debug("Teams Graph API: fetching messages", {
1037
+ conversationId: baseConversationId,
1038
+ threadMessageId,
1039
+ hasChannelContext: !!channelContext,
1040
+ limit,
1041
+ cursor,
1042
+ direction
1043
+ });
1044
+ if (channelContext && threadMessageId) {
1045
+ return this.fetchChannelThreadMessages(
1046
+ channelContext,
1047
+ threadMessageId,
1048
+ threadId,
1049
+ options
1050
+ );
1051
+ }
1052
+ let graphMessages;
1053
+ let hasMoreMessages = false;
1054
+ if (direction === "forward") {
1055
+ const allMessages = [];
1056
+ let nextLink;
1057
+ const apiUrl = `/chats/${encodeURIComponent(baseConversationId)}/messages`;
1058
+ do {
1059
+ const request = nextLink ? this.graphClient.api(nextLink) : this.graphClient.api(apiUrl).top(50).orderby("createdDateTime desc");
1060
+ const response = await request.get();
1061
+ const pageMessages = response.value || [];
1062
+ allMessages.push(...pageMessages);
1063
+ nextLink = response["@odata.nextLink"];
1064
+ } while (nextLink);
1065
+ allMessages.reverse();
1066
+ let startIndex = 0;
1067
+ if (cursor) {
1068
+ startIndex = allMessages.findIndex(
1069
+ (msg) => msg.createdDateTime && msg.createdDateTime > cursor
1070
+ );
1071
+ if (startIndex === -1) startIndex = allMessages.length;
1072
+ }
1073
+ hasMoreMessages = startIndex + limit < allMessages.length;
1074
+ graphMessages = allMessages.slice(startIndex, startIndex + limit);
1075
+ } else {
1076
+ let request = this.graphClient.api(`/chats/${encodeURIComponent(baseConversationId)}/messages`).top(limit).orderby("createdDateTime desc");
1077
+ if (cursor) {
1078
+ request = request.filter(`createdDateTime lt ${cursor}`);
1079
+ }
1080
+ const response = await request.get();
1081
+ graphMessages = response.value || [];
1082
+ graphMessages.reverse();
1083
+ hasMoreMessages = graphMessages.length >= limit;
1084
+ }
1085
+ if (threadMessageId && !channelContext) {
1086
+ graphMessages = graphMessages.filter((msg) => {
1087
+ return msg.id && msg.id >= threadMessageId;
1088
+ });
1089
+ this.logger?.debug("Filtered group chat messages to thread", {
1090
+ threadMessageId,
1091
+ filteredCount: graphMessages.length
1092
+ });
1093
+ }
1094
+ this.logger?.debug("Teams Graph API: fetched messages", {
1095
+ count: graphMessages.length,
1096
+ direction,
1097
+ hasMoreMessages
1098
+ });
1099
+ const messages = graphMessages.map((msg) => {
1100
+ const isFromBot = msg.from?.application?.id === this.config.appId || msg.from?.user?.id === this.config.appId;
1101
+ return {
1102
+ id: msg.id,
1103
+ threadId,
1104
+ text: this.extractTextFromGraphMessage(msg),
1105
+ formatted: this.formatConverter.toAst(
1106
+ this.extractTextFromGraphMessage(msg)
1107
+ ),
1108
+ raw: msg,
1109
+ author: {
1110
+ userId: msg.from?.user?.id || msg.from?.application?.id || "unknown",
1111
+ userName: msg.from?.user?.displayName || msg.from?.application?.displayName || "unknown",
1112
+ fullName: msg.from?.user?.displayName || msg.from?.application?.displayName || "unknown",
1113
+ isBot: !!msg.from?.application,
1114
+ isMe: isFromBot
1115
+ },
1116
+ metadata: {
1117
+ dateSent: msg.createdDateTime ? new Date(msg.createdDateTime) : /* @__PURE__ */ new Date(),
1118
+ edited: !!msg.lastModifiedDateTime
1119
+ },
1120
+ attachments: this.extractAttachmentsFromGraphMessage(msg)
1121
+ };
1122
+ });
1123
+ let nextCursor;
1124
+ if (hasMoreMessages && graphMessages.length > 0) {
1125
+ if (direction === "forward") {
1126
+ const lastMsg = graphMessages[graphMessages.length - 1];
1127
+ if (lastMsg?.createdDateTime) {
1128
+ nextCursor = lastMsg.createdDateTime;
1129
+ }
1130
+ } else {
1131
+ const oldestMsg = graphMessages[0];
1132
+ if (oldestMsg?.createdDateTime) {
1133
+ nextCursor = oldestMsg.createdDateTime;
1134
+ }
1135
+ }
1136
+ }
1137
+ return { messages, nextCursor };
1138
+ } catch (error) {
1139
+ this.logger?.error("Teams Graph API: fetchMessages error", { error });
1140
+ if (error instanceof Error && error.message?.includes("403")) {
1141
+ throw new NotImplementedError(
1142
+ "Teams fetchMessages requires one of these Azure AD app permissions: ChatMessage.Read.Chat, Chat.Read.All, or Chat.Read.WhereInstalled",
1143
+ "fetchMessages"
1144
+ );
1145
+ }
1146
+ throw error;
1147
+ }
1148
+ }
1149
+ /**
1150
+ * Fetch messages from a Teams channel thread using the channel-specific Graph API endpoint.
1151
+ * This provides proper thread-level filtering by fetching only replies to a specific message.
1152
+ *
1153
+ * Endpoint: GET /teams/{team-id}/channels/{channel-id}/messages/{message-id}/replies
1154
+ */
1155
+ async fetchChannelThreadMessages(context, threadMessageId, threadId, options) {
1156
+ const limit = options.limit || 50;
1157
+ const cursor = options.cursor;
1158
+ const direction = options.direction ?? "backward";
1159
+ this.logger?.debug("Teams Graph API: fetching channel thread messages", {
1160
+ teamId: context.teamId,
1161
+ channelId: context.channelId,
1162
+ threadMessageId,
1163
+ limit,
1164
+ cursor,
1165
+ direction
1166
+ });
1167
+ const parentUrl = `/teams/${encodeURIComponent(context.teamId)}/channels/${encodeURIComponent(context.channelId)}/messages/${encodeURIComponent(threadMessageId)}`;
1168
+ const repliesUrl = `${parentUrl}/replies`;
1169
+ const graphClient = this.graphClient;
1170
+ if (!graphClient) {
1171
+ throw new Error("Graph client not initialized");
1172
+ }
1173
+ let parentMessage = null;
1174
+ try {
1175
+ parentMessage = await graphClient.api(parentUrl).get();
1176
+ } catch (err) {
1177
+ this.logger?.warn("Failed to fetch parent message", {
1178
+ threadMessageId,
1179
+ err
1180
+ });
1181
+ }
1182
+ let graphMessages;
1183
+ let hasMoreMessages = false;
1184
+ if (direction === "forward") {
1185
+ const allReplies = [];
1186
+ let nextLink;
1187
+ do {
1188
+ const request = nextLink ? graphClient.api(nextLink) : graphClient.api(repliesUrl).top(50);
1189
+ const response = await request.get();
1190
+ const pageMessages = response.value || [];
1191
+ allReplies.push(...pageMessages);
1192
+ nextLink = response["@odata.nextLink"];
1193
+ } while (nextLink);
1194
+ allReplies.reverse();
1195
+ const allMessages = parentMessage ? [parentMessage, ...allReplies] : allReplies;
1196
+ let startIndex = 0;
1197
+ if (cursor) {
1198
+ startIndex = allMessages.findIndex(
1199
+ (msg) => msg.createdDateTime && msg.createdDateTime > cursor
1200
+ );
1201
+ if (startIndex === -1) startIndex = allMessages.length;
1202
+ }
1203
+ hasMoreMessages = startIndex + limit < allMessages.length;
1204
+ graphMessages = allMessages.slice(startIndex, startIndex + limit);
1205
+ } else {
1206
+ const allReplies = [];
1207
+ let nextLink;
1208
+ do {
1209
+ const request = nextLink ? graphClient.api(nextLink) : graphClient.api(repliesUrl).top(50);
1210
+ const response = await request.get();
1211
+ const pageMessages = response.value || [];
1212
+ allReplies.push(...pageMessages);
1213
+ nextLink = response["@odata.nextLink"];
1214
+ } while (nextLink);
1215
+ allReplies.reverse();
1216
+ const allMessages = parentMessage ? [parentMessage, ...allReplies] : allReplies;
1217
+ if (cursor) {
1218
+ const cursorIndex = allMessages.findIndex(
1219
+ (msg) => msg.createdDateTime && msg.createdDateTime >= cursor
1220
+ );
1221
+ if (cursorIndex > 0) {
1222
+ const sliceStart = Math.max(0, cursorIndex - limit);
1223
+ graphMessages = allMessages.slice(sliceStart, cursorIndex);
1224
+ hasMoreMessages = sliceStart > 0;
1225
+ } else {
1226
+ graphMessages = allMessages.slice(-limit);
1227
+ hasMoreMessages = allMessages.length > limit;
1228
+ }
1229
+ } else {
1230
+ graphMessages = allMessages.slice(-limit);
1231
+ hasMoreMessages = allMessages.length > limit;
1232
+ }
1233
+ }
1234
+ this.logger?.debug("Teams Graph API: fetched channel thread messages", {
1235
+ count: graphMessages.length,
1236
+ direction,
1237
+ hasMoreMessages
1238
+ });
1239
+ const messages = graphMessages.map((msg) => {
1240
+ const isFromBot = msg.from?.application?.id === this.config.appId || msg.from?.user?.id === this.config.appId;
1241
+ return {
1242
+ id: msg.id,
1243
+ threadId,
1244
+ text: this.extractTextFromGraphMessage(msg),
1245
+ formatted: this.formatConverter.toAst(
1246
+ this.extractTextFromGraphMessage(msg)
1247
+ ),
1248
+ raw: msg,
1249
+ author: {
1250
+ userId: msg.from?.user?.id || msg.from?.application?.id || "unknown",
1251
+ userName: msg.from?.user?.displayName || msg.from?.application?.displayName || "unknown",
1252
+ fullName: msg.from?.user?.displayName || msg.from?.application?.displayName || "unknown",
1253
+ isBot: !!msg.from?.application,
1254
+ isMe: isFromBot
1255
+ },
1256
+ metadata: {
1257
+ dateSent: msg.createdDateTime ? new Date(msg.createdDateTime) : /* @__PURE__ */ new Date(),
1258
+ edited: !!msg.lastModifiedDateTime
1259
+ },
1260
+ attachments: this.extractAttachmentsFromGraphMessage(msg)
1261
+ };
1262
+ });
1263
+ let nextCursor;
1264
+ if (hasMoreMessages && graphMessages.length > 0) {
1265
+ if (direction === "forward") {
1266
+ const lastMsg = graphMessages[graphMessages.length - 1];
1267
+ if (lastMsg?.createdDateTime) {
1268
+ nextCursor = lastMsg.createdDateTime;
1269
+ }
1270
+ } else {
1271
+ const oldestMsg = graphMessages[0];
1272
+ if (oldestMsg?.createdDateTime) {
1273
+ nextCursor = oldestMsg.createdDateTime;
1274
+ }
1275
+ }
1276
+ }
1277
+ return { messages, nextCursor };
1278
+ }
1279
+ /**
1280
+ * Extract plain text from a Graph API message.
1281
+ */
1282
+ extractTextFromGraphMessage(msg) {
1283
+ if (msg.body?.contentType === "text") {
1284
+ return msg.body.content || "";
1285
+ }
1286
+ let text = "";
1287
+ if (msg.body?.content) {
1288
+ text = msg.body.content.replace(/<[^>]*>/g, "").trim();
1289
+ }
1290
+ if (!text && msg.attachments?.length) {
1291
+ for (const att of msg.attachments) {
1292
+ if (att.contentType === "application/vnd.microsoft.card.adaptive") {
1293
+ try {
1294
+ const card = JSON.parse(att.content || "{}");
1295
+ const title = this.extractCardTitle(card);
1296
+ if (title) {
1297
+ return title;
1298
+ }
1299
+ return "[Card]";
1300
+ } catch {
1301
+ return "[Card]";
1302
+ }
1303
+ }
1304
+ }
1305
+ }
1306
+ return text;
1307
+ }
1308
+ /**
1309
+ * Extract a title/summary from an Adaptive Card structure.
1310
+ */
1311
+ extractCardTitle(card) {
1312
+ if (!card || typeof card !== "object") return null;
1313
+ const cardObj = card;
1314
+ if (Array.isArray(cardObj.body)) {
1315
+ for (const element of cardObj.body) {
1316
+ if (element && typeof element === "object" && element.type === "TextBlock") {
1317
+ const textBlock = element;
1318
+ if (textBlock.weight === "bolder" || textBlock.size === "large" || textBlock.size === "extraLarge") {
1319
+ const text = textBlock.text;
1320
+ if (typeof text === "string") {
1321
+ return text;
1322
+ }
1323
+ }
1324
+ }
1325
+ }
1326
+ for (const element of cardObj.body) {
1327
+ if (element && typeof element === "object" && element.type === "TextBlock") {
1328
+ const text = element.text;
1329
+ if (typeof text === "string") {
1330
+ return text;
1331
+ }
1332
+ }
1333
+ }
1334
+ }
1335
+ return null;
1336
+ }
1337
+ /**
1338
+ * Extract attachments from a Graph API message.
1339
+ */
1340
+ extractAttachmentsFromGraphMessage(msg) {
1341
+ if (!msg.attachments?.length) {
1342
+ return [];
1343
+ }
1344
+ return msg.attachments.map((att) => ({
1345
+ type: att.contentType?.includes("image") ? "image" : "file",
1346
+ name: att.name || void 0,
1347
+ url: att.contentUrl || void 0,
1348
+ mimeType: att.contentType || void 0
1349
+ }));
919
1350
  }
920
1351
  async fetchThread(threadId) {
921
1352
  const { conversationId } = this.decodeThreadId(threadId);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/cards.ts","../src/markdown.ts"],"sourcesContent":["import type { Activity, ConversationReference } from \"botbuilder\";\nimport {\n ActivityTypes,\n CloudAdapter,\n ConfigurationBotFrameworkAuthentication,\n type TurnContext,\n} from \"botbuilder\";\n\n/** Extended CloudAdapter that exposes processActivity for serverless environments */\nclass ServerlessCloudAdapter extends CloudAdapter {\n handleActivity(\n authHeader: string,\n activity: Activity,\n logic: (context: TurnContext) => Promise<void>,\n ) {\n return this.processActivity(authHeader, activity, logic);\n }\n}\n\nimport type {\n ActionEvent,\n Adapter,\n Attachment,\n ChatInstance,\n EmojiValue,\n FetchOptions,\n FileUpload,\n FormattedContent,\n Logger,\n Message,\n PostableMessage,\n RawMessage,\n ReactionEvent,\n ThreadInfo,\n WebhookOptions,\n} from \"chat\";\nimport {\n convertEmojiPlaceholders,\n defaultEmojiResolver,\n isCardElement,\n NotImplementedError,\n} from \"chat\";\nimport { cardToAdaptiveCard } from \"./cards\";\nimport { TeamsFormatConverter } from \"./markdown\";\n\nexport interface TeamsAdapterConfig {\n /** Microsoft App ID */\n appId: string;\n /** Microsoft App Password */\n appPassword: string;\n /** Microsoft App Type */\n appType?: \"MultiTenant\" | \"SingleTenant\";\n /** Microsoft App Tenant ID */\n appTenantId?: string;\n /** Override bot username (optional) */\n userName?: string;\n}\n\n/** Teams-specific thread ID data */\nexport interface TeamsThreadId {\n conversationId: string;\n serviceUrl: string;\n replyToId?: string;\n}\n\nexport class TeamsAdapter implements Adapter<TeamsThreadId, unknown> {\n readonly name = \"teams\";\n readonly userName: string;\n readonly botUserId?: string;\n\n private botAdapter: ServerlessCloudAdapter;\n private chat: ChatInstance | null = null;\n private logger: Logger | null = null;\n private formatConverter = new TeamsFormatConverter();\n private config: TeamsAdapterConfig;\n\n constructor(config: TeamsAdapterConfig) {\n this.config = config;\n this.userName = config.userName || \"bot\";\n\n if (config.appType === \"SingleTenant\" && !config.appTenantId) {\n throw new Error(\"appTenantId is required for SingleTenant app type\");\n }\n\n // Pass empty config object, credentials go via factory\n const auth = new ConfigurationBotFrameworkAuthentication({\n MicrosoftAppId: config.appId,\n MicrosoftAppPassword: config.appPassword,\n MicrosoftAppType: config.appType || \"MultiTenant\",\n MicrosoftAppTenantId:\n config.appType === \"SingleTenant\" ? config.appTenantId : undefined,\n });\n\n this.botAdapter = new ServerlessCloudAdapter(auth);\n }\n\n async initialize(chat: ChatInstance): Promise<void> {\n this.chat = chat;\n this.logger = chat.getLogger(this.name);\n }\n\n async handleWebhook(\n request: Request,\n options?: WebhookOptions,\n ): Promise<Response> {\n const body = await request.text();\n this.logger?.debug(\"Teams webhook raw body\", { body });\n\n let activity: Activity;\n try {\n activity = JSON.parse(body);\n } catch (e) {\n this.logger?.error(\"Failed to parse request body\", { error: e });\n return new Response(\"Invalid JSON\", { status: 400 });\n }\n\n // Get the auth header for token validation\n const authHeader = request.headers.get(\"authorization\") || \"\";\n\n try {\n // Use handleActivity which takes the activity directly\n // instead of mocking Node.js req/res objects\n await this.botAdapter.handleActivity(\n authHeader,\n activity,\n async (context) => {\n await this.handleTurn(context, options);\n },\n );\n\n return new Response(JSON.stringify({}), {\n status: 200,\n headers: { \"Content-Type\": \"application/json\" },\n });\n } catch (error) {\n this.logger?.error(\"Bot adapter process error\", { error });\n return new Response(JSON.stringify({ error: \"Internal error\" }), {\n status: 500,\n headers: { \"Content-Type\": \"application/json\" },\n });\n }\n }\n\n private async handleTurn(\n context: TurnContext,\n options?: WebhookOptions,\n ): Promise<void> {\n if (!this.chat) {\n this.logger?.warn(\"Chat instance not initialized, ignoring event\");\n return;\n }\n\n const activity = context.activity;\n\n // Cache serviceUrl and tenantId for the user - needed for opening DMs later\n if (activity.from?.id && activity.serviceUrl) {\n const userId = activity.from.id;\n const tenantId = (activity.channelData as { tenant?: { id?: string } })\n ?.tenant?.id;\n const ttl = 30 * 24 * 60 * 60 * 1000; // 30 days\n\n // Store serviceUrl and tenantId for DM creation\n this.chat\n .getState()\n .set(`teams:serviceUrl:${userId}`, activity.serviceUrl, ttl);\n if (tenantId) {\n this.chat.getState().set(`teams:tenantId:${userId}`, tenantId, ttl);\n }\n }\n\n // Handle message reactions\n if (activity.type === ActivityTypes.MessageReaction) {\n this.handleReactionActivity(activity, options);\n return;\n }\n\n // Handle adaptive card actions (button clicks)\n if (activity.type === ActivityTypes.Invoke) {\n await this.handleInvokeActivity(context, options);\n return;\n }\n\n // Only handle message activities\n if (activity.type !== ActivityTypes.Message) {\n this.logger?.debug(\"Ignoring non-message activity\", {\n type: activity.type,\n });\n return;\n }\n\n // Check if this message activity is actually a button click (Action.Submit)\n // Teams sends Action.Submit as a message with value.actionId\n const actionValue = activity.value as\n | { actionId?: string; value?: string }\n | undefined;\n if (actionValue?.actionId) {\n this.handleMessageAction(activity, actionValue, options);\n return;\n }\n\n const threadId = this.encodeThreadId({\n conversationId: activity.conversation?.id || \"\",\n serviceUrl: activity.serviceUrl || \"\",\n replyToId: activity.replyToId,\n });\n\n // Let Chat class handle async processing and waitUntil\n this.chat.processMessage(\n this,\n threadId,\n this.parseTeamsMessage(activity, threadId),\n options,\n );\n }\n\n /**\n * Handle Action.Submit button clicks sent as message activities.\n * Teams sends these with type \"message\" and value.actionId.\n */\n private handleMessageAction(\n activity: Activity,\n actionValue: { actionId?: string; value?: string },\n options?: WebhookOptions,\n ): void {\n if (!this.chat || !actionValue.actionId) return;\n\n const threadId = this.encodeThreadId({\n conversationId: activity.conversation?.id || \"\",\n serviceUrl: activity.serviceUrl || \"\",\n });\n\n const actionEvent: Omit<ActionEvent, \"thread\"> & { adapter: TeamsAdapter } =\n {\n actionId: actionValue.actionId,\n value: actionValue.value,\n user: {\n userId: activity.from?.id || \"unknown\",\n userName: activity.from?.name || \"unknown\",\n fullName: activity.from?.name || \"unknown\",\n isBot: false,\n isMe: false,\n },\n messageId: activity.replyToId || activity.id || \"\",\n threadId,\n adapter: this,\n raw: activity,\n };\n\n this.logger?.debug(\"Processing Teams message action (Action.Submit)\", {\n actionId: actionValue.actionId,\n value: actionValue.value,\n messageId: actionEvent.messageId,\n threadId,\n });\n\n this.chat.processAction(actionEvent, options);\n }\n\n /**\n * Handle invoke activities (adaptive card actions, etc.).\n */\n private async handleInvokeActivity(\n context: TurnContext,\n options?: WebhookOptions,\n ): Promise<void> {\n const activity = context.activity;\n\n // Handle adaptive card action invokes\n if (activity.name === \"adaptiveCard/action\") {\n await this.handleAdaptiveCardAction(context, activity, options);\n return;\n }\n\n this.logger?.debug(\"Ignoring unsupported invoke\", {\n name: activity.name,\n });\n }\n\n /**\n * Handle adaptive card button clicks.\n * The action data is in activity.value with our { actionId, value } structure.\n */\n private async handleAdaptiveCardAction(\n context: TurnContext,\n activity: Activity,\n options?: WebhookOptions,\n ): Promise<void> {\n if (!this.chat) return;\n\n // Activity.value contains our action data\n const actionData = activity.value?.action?.data as\n | { actionId?: string; value?: string }\n | undefined;\n\n if (!actionData?.actionId) {\n this.logger?.debug(\"Adaptive card action missing actionId\", {\n value: activity.value,\n });\n // Send acknowledgment response\n await context.sendActivity({\n type: ActivityTypes.InvokeResponse,\n value: { status: 200 },\n });\n return;\n }\n\n const threadId = this.encodeThreadId({\n conversationId: activity.conversation?.id || \"\",\n serviceUrl: activity.serviceUrl || \"\",\n });\n\n const actionEvent: Omit<ActionEvent, \"thread\"> & { adapter: TeamsAdapter } =\n {\n actionId: actionData.actionId,\n value: actionData.value,\n user: {\n userId: activity.from?.id || \"unknown\",\n userName: activity.from?.name || \"unknown\",\n fullName: activity.from?.name || \"unknown\",\n isBot: false,\n isMe: false,\n },\n messageId: activity.replyToId || activity.id || \"\",\n threadId,\n adapter: this,\n raw: activity,\n };\n\n this.logger?.debug(\"Processing Teams adaptive card action\", {\n actionId: actionData.actionId,\n value: actionData.value,\n messageId: actionEvent.messageId,\n threadId,\n });\n\n this.chat.processAction(actionEvent, options);\n\n // Send acknowledgment response to prevent timeout\n await context.sendActivity({\n type: ActivityTypes.InvokeResponse,\n value: { status: 200 },\n });\n }\n\n /**\n * Handle Teams reaction events (reactionsAdded/reactionsRemoved).\n */\n private handleReactionActivity(\n activity: Activity,\n options?: WebhookOptions,\n ): void {\n if (!this.chat) return;\n\n // Extract the message ID from conversation ID\n // Format: \"19:xxx@thread.tacv2;messageid=1767297849909\"\n const conversationId = activity.conversation?.id || \"\";\n const messageIdMatch = conversationId.match(/messageid=(\\d+)/);\n const messageId = messageIdMatch?.[1] || activity.replyToId || \"\";\n\n // Build thread ID - KEEP the full conversation ID including ;messageid=XXX\n // This is required for Teams to reply in the correct thread\n const threadId = this.encodeThreadId({\n conversationId: conversationId,\n serviceUrl: activity.serviceUrl || \"\",\n });\n\n const user = {\n userId: activity.from?.id || \"unknown\",\n userName: activity.from?.name || \"unknown\",\n fullName: activity.from?.name,\n isBot: false,\n isMe: this.isMessageFromSelf(activity),\n };\n\n // Process added reactions\n const reactionsAdded = activity.reactionsAdded || [];\n for (const reaction of reactionsAdded) {\n const rawEmoji = reaction.type || \"\";\n const emojiValue = defaultEmojiResolver.fromTeams(rawEmoji);\n\n const event: Omit<ReactionEvent, \"adapter\" | \"thread\"> = {\n emoji: emojiValue,\n rawEmoji,\n added: true,\n user,\n messageId,\n threadId,\n raw: activity,\n };\n\n this.logger?.debug(\"Processing Teams reaction added\", {\n emoji: emojiValue.name,\n rawEmoji,\n messageId,\n });\n\n this.chat.processReaction({ ...event, adapter: this }, options);\n }\n\n // Process removed reactions\n const reactionsRemoved = activity.reactionsRemoved || [];\n for (const reaction of reactionsRemoved) {\n const rawEmoji = reaction.type || \"\";\n const emojiValue = defaultEmojiResolver.fromTeams(rawEmoji);\n\n const event: Omit<ReactionEvent, \"adapter\" | \"thread\"> = {\n emoji: emojiValue,\n rawEmoji,\n added: false,\n user,\n messageId,\n threadId,\n raw: activity,\n };\n\n this.logger?.debug(\"Processing Teams reaction removed\", {\n emoji: emojiValue.name,\n rawEmoji,\n messageId,\n });\n\n this.chat.processReaction({ ...event, adapter: this }, options);\n }\n }\n\n private parseTeamsMessage(\n activity: Activity,\n threadId: string,\n ): Message<unknown> {\n const text = activity.text || \"\";\n // Normalize mentions - format converter will convert <at>name</at> to @name\n const normalizedText = this.normalizeMentions(text, activity);\n\n const isMe = this.isMessageFromSelf(activity);\n\n return {\n id: activity.id || \"\",\n threadId,\n text: this.formatConverter.extractPlainText(normalizedText),\n formatted: this.formatConverter.toAst(normalizedText),\n raw: activity,\n author: {\n userId: activity.from?.id || \"unknown\",\n userName: activity.from?.name || \"unknown\",\n fullName: activity.from?.name || \"unknown\",\n isBot: activity.from?.role === \"bot\",\n isMe,\n },\n metadata: {\n dateSent: activity.timestamp\n ? new Date(activity.timestamp)\n : new Date(),\n edited: false,\n },\n attachments: (activity.attachments || [])\n .filter(\n (att) =>\n att.contentType !== \"application/vnd.microsoft.card.adaptive\",\n )\n .map((att) => this.createAttachment(att)),\n };\n }\n\n /**\n * Create an Attachment object from a Teams attachment.\n */\n private createAttachment(att: {\n contentType?: string;\n contentUrl?: string;\n name?: string;\n }): Attachment {\n const url = att.contentUrl;\n\n // Determine type based on contentType\n let type: Attachment[\"type\"] = \"file\";\n if (att.contentType?.startsWith(\"image/\")) {\n type = \"image\";\n } else if (att.contentType?.startsWith(\"video/\")) {\n type = \"video\";\n } else if (att.contentType?.startsWith(\"audio/\")) {\n type = \"audio\";\n }\n\n return {\n type,\n url,\n name: att.name,\n mimeType: att.contentType,\n fetchData: url\n ? async () => {\n const response = await fetch(url);\n if (!response.ok) {\n throw new Error(\n `Failed to fetch file: ${response.status} ${response.statusText}`,\n );\n }\n const arrayBuffer = await response.arrayBuffer();\n return Buffer.from(arrayBuffer);\n }\n : undefined,\n };\n }\n\n private normalizeMentions(text: string, _activity: Activity): string {\n // Don't strip mentions - the format converter will convert <at>name</at> to @name\n // Just trim any leading/trailing whitespace that might result from mention placement\n return text.trim();\n }\n\n async postMessage(\n threadId: string,\n message: PostableMessage,\n ): Promise<RawMessage<unknown>> {\n const { conversationId, serviceUrl } = this.decodeThreadId(threadId);\n\n // Check for files to upload\n const files = this.extractFiles(message);\n const fileAttachments =\n files.length > 0 ? await this.filesToAttachments(files) : [];\n\n // Check if message contains a card\n const card = this.extractCard(message);\n let activity: Partial<Activity>;\n\n if (card) {\n // Render card as Adaptive Card\n const adaptiveCard = cardToAdaptiveCard(card);\n\n activity = {\n type: ActivityTypes.Message,\n // Don't include text - Teams shows both text and card if text is present\n attachments: [\n {\n contentType: \"application/vnd.microsoft.card.adaptive\",\n content: adaptiveCard,\n },\n ...fileAttachments,\n ],\n };\n\n this.logger?.debug(\"Teams API: sendActivity (adaptive card)\", {\n conversationId,\n serviceUrl,\n fileCount: fileAttachments.length,\n });\n } else {\n // Regular text message\n const text = convertEmojiPlaceholders(\n this.formatConverter.renderPostable(message),\n \"teams\",\n );\n\n activity = {\n type: ActivityTypes.Message,\n text,\n textFormat: \"markdown\",\n attachments: fileAttachments.length > 0 ? fileAttachments : undefined,\n };\n\n this.logger?.debug(\"Teams API: sendActivity (message)\", {\n conversationId,\n serviceUrl,\n textLength: text.length,\n fileCount: fileAttachments.length,\n });\n }\n\n // Use the adapter to send the message\n const conversationReference = {\n channelId: \"msteams\",\n serviceUrl,\n conversation: { id: conversationId },\n };\n\n let messageId = \"\";\n\n await this.botAdapter.continueConversationAsync(\n this.config.appId,\n conversationReference as Partial<ConversationReference>,\n async (context) => {\n const response = await context.sendActivity(activity);\n messageId = response?.id || \"\";\n },\n );\n\n this.logger?.debug(\"Teams API: sendActivity response\", { messageId });\n\n return {\n id: messageId,\n threadId,\n raw: activity,\n };\n }\n\n /**\n * Extract card element from a PostableMessage if present.\n */\n private extractCard(\n message: PostableMessage,\n ): import(\"chat\").CardElement | null {\n if (isCardElement(message)) {\n return message;\n }\n if (typeof message === \"object\" && message !== null && \"card\" in message) {\n return message.card;\n }\n return null;\n }\n\n /**\n * Extract files from a PostableMessage if present.\n */\n private extractFiles(message: PostableMessage): FileUpload[] {\n if (typeof message === \"object\" && message !== null && \"files\" in message) {\n return (message as { files?: FileUpload[] }).files ?? [];\n }\n return [];\n }\n\n /**\n * Convert files to Teams attachments.\n * Uses inline data URIs for small files.\n */\n private async filesToAttachments(\n files: FileUpload[],\n ): Promise<Array<{ contentType: string; contentUrl: string; name: string }>> {\n const attachments: Array<{\n contentType: string;\n contentUrl: string;\n name: string;\n }> = [];\n\n for (const file of files) {\n // Convert data to Buffer\n let buffer: Buffer;\n if (Buffer.isBuffer(file.data)) {\n buffer = file.data;\n } else if (file.data instanceof ArrayBuffer) {\n buffer = Buffer.from(file.data);\n } else if (file.data instanceof Blob) {\n const arrayBuffer = await file.data.arrayBuffer();\n buffer = Buffer.from(arrayBuffer);\n } else {\n continue;\n }\n\n // Create data URI\n const mimeType = file.mimeType || \"application/octet-stream\";\n const base64 = buffer.toString(\"base64\");\n const dataUri = `data:${mimeType};base64,${base64}`;\n\n attachments.push({\n contentType: mimeType,\n contentUrl: dataUri,\n name: file.filename,\n });\n }\n\n return attachments;\n }\n\n async editMessage(\n threadId: string,\n messageId: string,\n message: PostableMessage,\n ): Promise<RawMessage<unknown>> {\n const { conversationId, serviceUrl } = this.decodeThreadId(threadId);\n\n // Check if message contains a card\n const card = this.extractCard(message);\n let activity: Partial<Activity>;\n\n if (card) {\n // Render card as Adaptive Card\n const adaptiveCard = cardToAdaptiveCard(card);\n\n activity = {\n id: messageId,\n type: ActivityTypes.Message,\n // Don't include text - Teams shows both text and card if text is present\n attachments: [\n {\n contentType: \"application/vnd.microsoft.card.adaptive\",\n content: adaptiveCard,\n },\n ],\n };\n\n this.logger?.debug(\"Teams API: updateActivity (adaptive card)\", {\n conversationId,\n messageId,\n });\n } else {\n // Regular text message\n const text = convertEmojiPlaceholders(\n this.formatConverter.renderPostable(message),\n \"teams\",\n );\n\n activity = {\n id: messageId,\n type: ActivityTypes.Message,\n text,\n textFormat: \"markdown\",\n };\n\n this.logger?.debug(\"Teams API: updateActivity\", {\n conversationId,\n messageId,\n textLength: text.length,\n });\n }\n\n const conversationReference = {\n channelId: \"msteams\",\n serviceUrl,\n conversation: { id: conversationId },\n };\n\n await this.botAdapter.continueConversationAsync(\n this.config.appId,\n conversationReference as Partial<ConversationReference>,\n async (context) => {\n await context.updateActivity(activity);\n },\n );\n\n this.logger?.debug(\"Teams API: updateActivity response\", { ok: true });\n\n return {\n id: messageId,\n threadId,\n raw: activity,\n };\n }\n\n async deleteMessage(threadId: string, messageId: string): Promise<void> {\n const { conversationId, serviceUrl } = this.decodeThreadId(threadId);\n\n const conversationReference = {\n channelId: \"msteams\",\n serviceUrl,\n conversation: { id: conversationId },\n };\n\n this.logger?.debug(\"Teams API: deleteActivity\", {\n conversationId,\n messageId,\n });\n\n await this.botAdapter.continueConversationAsync(\n this.config.appId,\n conversationReference as Partial<ConversationReference>,\n async (context) => {\n await context.deleteActivity(messageId);\n },\n );\n\n this.logger?.debug(\"Teams API: deleteActivity response\", { ok: true });\n }\n\n async addReaction(\n _threadId: string,\n _messageId: string,\n _emoji: EmojiValue | string,\n ): Promise<void> {\n throw new NotImplementedError(\n \"Teams Bot Framework does not expose reaction APIs\",\n \"addReaction\",\n );\n }\n\n async removeReaction(\n _threadId: string,\n _messageId: string,\n _emoji: EmojiValue | string,\n ): Promise<void> {\n throw new NotImplementedError(\n \"Teams Bot Framework does not expose reaction APIs\",\n \"removeReaction\",\n );\n }\n\n async startTyping(threadId: string): Promise<void> {\n const { conversationId, serviceUrl } = this.decodeThreadId(threadId);\n\n const conversationReference = {\n channelId: \"msteams\",\n serviceUrl,\n conversation: { id: conversationId },\n };\n\n this.logger?.debug(\"Teams API: sendActivity (typing)\", { conversationId });\n\n await this.botAdapter.continueConversationAsync(\n this.config.appId,\n conversationReference as Partial<ConversationReference>,\n async (context) => {\n await context.sendActivity({ type: ActivityTypes.Typing });\n },\n );\n\n this.logger?.debug(\"Teams API: sendActivity (typing) response\", {\n ok: true,\n });\n }\n\n /**\n * Open a direct message conversation with a user.\n * Returns a thread ID that can be used to post messages.\n *\n * The serviceUrl and tenantId are automatically resolved from cached user interactions.\n * If no cached values are found, defaults are used (which may not work for all tenants).\n */\n async openDM(userId: string): Promise<string> {\n // Look up cached serviceUrl and tenantId for this user from state\n const cachedServiceUrl = await this.chat\n ?.getState()\n .get<string>(`teams:serviceUrl:${userId}`);\n const cachedTenantId = await this.chat\n ?.getState()\n .get<string>(`teams:tenantId:${userId}`);\n\n const serviceUrl =\n cachedServiceUrl || \"https://smba.trafficmanager.net/teams/\";\n // Use cached tenant ID, config tenant ID, or undefined (will fail for multi-tenant)\n const tenantId = cachedTenantId || this.config.appTenantId;\n\n this.logger?.debug(\"Teams: creating 1:1 conversation\", {\n userId,\n serviceUrl,\n tenantId,\n cachedServiceUrl: !!cachedServiceUrl,\n cachedTenantId: !!cachedTenantId,\n });\n\n if (!tenantId) {\n throw new Error(\n \"Cannot open DM: tenant ID not found. User must interact with the bot first (via @mention) to cache their tenant ID.\",\n );\n }\n\n let conversationId = \"\";\n\n // Create the 1:1 conversation using createConversationAsync\n // The conversation ID is captured from within the callback, not from the return value\n // biome-ignore lint/suspicious/noExplicitAny: BotBuilder types are incomplete\n await (this.botAdapter as any).createConversationAsync(\n this.config.appId,\n \"msteams\",\n serviceUrl,\n \"\", // empty audience\n {\n isGroup: false,\n bot: { id: this.config.appId, name: this.userName },\n members: [{ id: userId }],\n tenantId,\n channelData: {\n tenant: { id: tenantId },\n },\n },\n async (turnContext: TurnContext) => {\n // Capture the conversation ID from the new context\n conversationId = turnContext?.activity?.conversation?.id || \"\";\n this.logger?.debug(\"Teams: conversation created in callback\", {\n conversationId,\n activityId: turnContext?.activity?.id,\n });\n },\n );\n\n if (!conversationId) {\n throw new Error(\"Failed to create 1:1 conversation - no ID returned\");\n }\n\n this.logger?.debug(\"Teams: 1:1 conversation created\", { conversationId });\n\n return this.encodeThreadId({\n conversationId,\n serviceUrl,\n });\n }\n\n async fetchMessages(\n _threadId: string,\n _options: FetchOptions = {},\n ): Promise<Message<unknown>[]> {\n throw new NotImplementedError(\n \"Teams does not provide a bot API to fetch message history. Use Microsoft Graph API instead.\",\n \"fetchMessages\",\n );\n }\n\n async fetchThread(threadId: string): Promise<ThreadInfo> {\n const { conversationId } = this.decodeThreadId(threadId);\n\n return {\n id: threadId,\n channelId: conversationId,\n metadata: {},\n };\n }\n\n encodeThreadId(platformData: TeamsThreadId): string {\n // Base64 encode both since conversationId and serviceUrl can contain special characters\n const encodedConversationId = Buffer.from(\n platformData.conversationId,\n ).toString(\"base64url\");\n const encodedServiceUrl = Buffer.from(platformData.serviceUrl).toString(\n \"base64url\",\n );\n return `teams:${encodedConversationId}:${encodedServiceUrl}`;\n }\n\n /**\n * Check if a thread is a direct message conversation.\n * Teams DMs have conversation IDs that don't start with \"19:\" (which is for groups/channels).\n */\n isDM(threadId: string): boolean {\n const { conversationId } = this.decodeThreadId(threadId);\n // Group chats and channels start with \"19:\", DMs don't\n return !conversationId.startsWith(\"19:\");\n }\n\n decodeThreadId(threadId: string): TeamsThreadId {\n const parts = threadId.split(\":\");\n if (parts.length !== 3 || parts[0] !== \"teams\") {\n throw new Error(`Invalid Teams thread ID: ${threadId}`);\n }\n const conversationId = Buffer.from(\n parts[1] as string,\n \"base64url\",\n ).toString(\"utf-8\");\n const serviceUrl = Buffer.from(parts[2] as string, \"base64url\").toString(\n \"utf-8\",\n );\n return { conversationId, serviceUrl };\n }\n\n parseMessage(raw: unknown): Message<unknown> {\n const activity = raw as Activity;\n const threadId = this.encodeThreadId({\n conversationId: activity.conversation?.id || \"\",\n serviceUrl: activity.serviceUrl || \"\",\n });\n return this.parseTeamsMessage(activity, threadId);\n }\n\n /**\n * Check if a Teams activity is from this bot.\n *\n * Teams bot IDs can appear in different formats:\n * - Just the app ID: \"abc123-def456-...\"\n * - With prefix: \"28:abc123-def456-...\"\n *\n * We check both exact match and suffix match (after colon delimiter)\n * to handle all formats safely.\n */\n private isMessageFromSelf(activity: Activity): boolean {\n const fromId = activity.from?.id;\n if (!fromId || !this.config.appId) {\n return false;\n }\n\n // Exact match (bot ID is just the app ID)\n if (fromId === this.config.appId) {\n return true;\n }\n\n // Teams format: \"28:{appId}\" or similar prefix patterns\n // Check if it ends with our appId after a colon delimiter\n if (fromId.endsWith(`:${this.config.appId}`)) {\n return true;\n }\n\n return false;\n }\n\n renderFormatted(content: FormattedContent): string {\n return this.formatConverter.fromAst(content);\n }\n}\n\nexport function createTeamsAdapter(config: TeamsAdapterConfig): TeamsAdapter {\n return new TeamsAdapter(config);\n}\n\n// Re-export card converter for advanced use\nexport { cardToAdaptiveCard, cardToFallbackText } from \"./cards\";\nexport { TeamsFormatConverter } from \"./markdown\";\n","/**\n * Teams Adaptive Card converter for cross-platform cards.\n *\n * Converts CardElement to Microsoft Adaptive Cards format.\n * @see https://adaptivecards.io/\n */\n\nimport {\n type ActionsElement,\n type ButtonElement,\n type CardChild,\n type CardElement,\n convertEmojiPlaceholders,\n type DividerElement,\n type FieldsElement,\n type ImageElement,\n type SectionElement,\n type TextElement,\n} from \"chat\";\n\n/**\n * Convert emoji placeholders in text to Teams format.\n */\nfunction convertEmoji(text: string): string {\n return convertEmojiPlaceholders(text, \"teams\");\n}\n\n// Adaptive Card types (simplified)\nexport interface AdaptiveCard {\n type: \"AdaptiveCard\";\n $schema: string;\n version: string;\n body: AdaptiveCardElement[];\n actions?: AdaptiveCardAction[];\n}\n\nexport interface AdaptiveCardElement {\n type: string;\n [key: string]: unknown;\n}\n\nexport interface AdaptiveCardAction {\n type: string;\n title: string;\n data?: Record<string, unknown>;\n style?: string;\n}\n\nconst ADAPTIVE_CARD_SCHEMA =\n \"http://adaptivecards.io/schemas/adaptive-card.json\";\nconst ADAPTIVE_CARD_VERSION = \"1.4\";\n\n/**\n * Convert a CardElement to a Teams Adaptive Card.\n */\nexport function cardToAdaptiveCard(card: CardElement): AdaptiveCard {\n const body: AdaptiveCardElement[] = [];\n const actions: AdaptiveCardAction[] = [];\n\n // Add title as TextBlock\n if (card.title) {\n body.push({\n type: \"TextBlock\",\n text: convertEmoji(card.title),\n weight: \"bolder\",\n size: \"large\",\n wrap: true,\n });\n }\n\n // Add subtitle as TextBlock\n if (card.subtitle) {\n body.push({\n type: \"TextBlock\",\n text: convertEmoji(card.subtitle),\n isSubtle: true,\n wrap: true,\n });\n }\n\n // Add header image if present\n if (card.imageUrl) {\n body.push({\n type: \"Image\",\n url: card.imageUrl,\n size: \"stretch\",\n });\n }\n\n // Convert children\n for (const child of card.children) {\n const result = convertChildToAdaptive(child);\n body.push(...result.elements);\n actions.push(...result.actions);\n }\n\n const adaptiveCard: AdaptiveCard = {\n type: \"AdaptiveCard\",\n $schema: ADAPTIVE_CARD_SCHEMA,\n version: ADAPTIVE_CARD_VERSION,\n body,\n };\n\n if (actions.length > 0) {\n adaptiveCard.actions = actions;\n }\n\n return adaptiveCard;\n}\n\ninterface ConvertResult {\n elements: AdaptiveCardElement[];\n actions: AdaptiveCardAction[];\n}\n\n/**\n * Convert a card child element to Adaptive Card elements.\n */\nfunction convertChildToAdaptive(child: CardChild): ConvertResult {\n switch (child.type) {\n case \"text\":\n return { elements: [convertTextToElement(child)], actions: [] };\n case \"image\":\n return { elements: [convertImageToElement(child)], actions: [] };\n case \"divider\":\n return { elements: [convertDividerToElement(child)], actions: [] };\n case \"actions\":\n return convertActionsToElements(child);\n case \"section\":\n return convertSectionToElements(child);\n case \"fields\":\n return { elements: [convertFieldsToElement(child)], actions: [] };\n default:\n return { elements: [], actions: [] };\n }\n}\n\nfunction convertTextToElement(element: TextElement): AdaptiveCardElement {\n const textBlock: AdaptiveCardElement = {\n type: \"TextBlock\",\n text: convertEmoji(element.content),\n wrap: true,\n };\n\n if (element.style === \"bold\") {\n textBlock.weight = \"bolder\";\n } else if (element.style === \"muted\") {\n textBlock.isSubtle = true;\n }\n\n return textBlock;\n}\n\nfunction convertImageToElement(element: ImageElement): AdaptiveCardElement {\n return {\n type: \"Image\",\n url: element.url,\n altText: element.alt || \"Image\",\n size: \"auto\",\n };\n}\n\nfunction convertDividerToElement(\n _element: DividerElement,\n): AdaptiveCardElement {\n // Adaptive Cards don't have a native divider, use a separator container\n return {\n type: \"Container\",\n separator: true,\n items: [],\n };\n}\n\nfunction convertActionsToElements(element: ActionsElement): ConvertResult {\n // In Adaptive Cards, actions go at the card level, not inline\n const actions: AdaptiveCardAction[] = element.children.map((button) =>\n convertButtonToAction(button),\n );\n\n return { elements: [], actions };\n}\n\nfunction convertButtonToAction(button: ButtonElement): AdaptiveCardAction {\n const action: AdaptiveCardAction = {\n type: \"Action.Submit\",\n title: convertEmoji(button.label),\n data: {\n actionId: button.id,\n value: button.value,\n },\n };\n\n if (button.style === \"primary\") {\n action.style = \"positive\";\n } else if (button.style === \"danger\") {\n action.style = \"destructive\";\n }\n\n return action;\n}\n\nfunction convertSectionToElements(element: SectionElement): ConvertResult {\n const elements: AdaptiveCardElement[] = [];\n const actions: AdaptiveCardAction[] = [];\n\n // Wrap section in a container\n const containerItems: AdaptiveCardElement[] = [];\n\n for (const child of element.children) {\n const result = convertChildToAdaptive(child);\n containerItems.push(...result.elements);\n actions.push(...result.actions);\n }\n\n if (containerItems.length > 0) {\n elements.push({\n type: \"Container\",\n items: containerItems,\n });\n }\n\n return { elements, actions };\n}\n\nfunction convertFieldsToElement(element: FieldsElement): AdaptiveCardElement {\n // Use FactSet for key-value pairs\n const facts = element.children.map((field) => ({\n title: convertEmoji(field.label),\n value: convertEmoji(field.value),\n }));\n\n return {\n type: \"FactSet\",\n facts,\n };\n}\n\n/**\n * Generate fallback text from a card element.\n * Used when adaptive cards aren't supported.\n */\nexport function cardToFallbackText(card: CardElement): string {\n const parts: string[] = [];\n\n if (card.title) {\n parts.push(`**${convertEmoji(card.title)}**`);\n }\n\n if (card.subtitle) {\n parts.push(convertEmoji(card.subtitle));\n }\n\n for (const child of card.children) {\n const text = childToFallbackText(child);\n if (text) {\n parts.push(text);\n }\n }\n\n return parts.join(\"\\n\\n\");\n}\n\nfunction childToFallbackText(child: CardChild): string | null {\n switch (child.type) {\n case \"text\":\n return convertEmoji(child.content);\n case \"fields\":\n return child.children\n .map((f) => `**${convertEmoji(f.label)}**: ${convertEmoji(f.value)}`)\n .join(\"\\n\");\n case \"actions\":\n return `[${child.children.map((b) => convertEmoji(b.label)).join(\"] [\")}]`;\n case \"section\":\n return child.children\n .map((c) => childToFallbackText(c))\n .filter(Boolean)\n .join(\"\\n\");\n default:\n return null;\n }\n}\n","/**\n * Teams-specific format conversion using AST-based parsing.\n *\n * Teams supports a subset of HTML for formatting:\n * - Bold: <b> or <strong>\n * - Italic: <i> or <em>\n * - Strikethrough: <s> or <strike>\n * - Links: <a href=\"url\">text</a>\n * - Code: <pre> and <code>\n *\n * Teams also accepts standard markdown in most cases.\n */\n\nimport {\n BaseFormatConverter,\n type Code,\n type Content,\n type Delete,\n type Emphasis,\n type InlineCode,\n type Link,\n type Paragraph,\n type PostableMessage,\n parseMarkdown,\n type Root,\n type Strong,\n type Text,\n} from \"chat\";\n\nexport class TeamsFormatConverter extends BaseFormatConverter {\n /**\n * Convert @mentions to Teams format in plain text.\n * @name → <at>name</at>\n */\n private convertMentionsToTeams(text: string): string {\n return text.replace(/@(\\w+)/g, \"<at>$1</at>\");\n }\n\n /**\n * Override renderPostable to convert @mentions in plain strings.\n */\n override renderPostable(message: PostableMessage): string {\n if (typeof message === \"string\") {\n return this.convertMentionsToTeams(message);\n }\n if (\"raw\" in message) {\n return this.convertMentionsToTeams(message.raw);\n }\n if (\"markdown\" in message) {\n return this.fromAst(parseMarkdown(message.markdown));\n }\n if (\"ast\" in message) {\n return this.fromAst(message.ast);\n }\n return \"\";\n }\n\n /**\n * Render an AST to Teams format.\n * Teams accepts standard markdown, so we just stringify cleanly.\n */\n fromAst(ast: Root): string {\n const parts: string[] = [];\n\n for (const node of ast.children) {\n parts.push(this.nodeToTeams(node as Content));\n }\n\n return parts.join(\"\\n\\n\");\n }\n\n /**\n * Parse Teams message into an AST.\n * Converts Teams HTML/mentions to standard markdown format.\n */\n toAst(teamsText: string): Root {\n // Convert Teams HTML to markdown, then parse\n let markdown = teamsText;\n\n // Convert @mentions from Teams format: <at>Name</at> -> @Name\n markdown = markdown.replace(/<at>([^<]+)<\\/at>/gi, \"@$1\");\n\n // Convert HTML tags to markdown\n // Bold: <b>, <strong> -> **text**\n markdown = markdown.replace(\n /<(b|strong)>([^<]+)<\\/(b|strong)>/gi,\n \"**$2**\",\n );\n\n // Italic: <i>, <em> -> _text_\n markdown = markdown.replace(/<(i|em)>([^<]+)<\\/(i|em)>/gi, \"_$2_\");\n\n // Strikethrough: <s>, <strike> -> ~~text~~\n markdown = markdown.replace(\n /<(s|strike)>([^<]+)<\\/(s|strike)>/gi,\n \"~~$2~~\",\n );\n\n // Links: <a href=\"url\">text</a> -> [text](url)\n markdown = markdown.replace(\n /<a[^>]+href=\"([^\"]+)\"[^>]*>([^<]+)<\\/a>/gi,\n \"[$2]($1)\",\n );\n\n // Code: <code>text</code> -> `text`\n markdown = markdown.replace(/<code>([^<]+)<\\/code>/gi, \"`$1`\");\n\n // Pre: <pre>text</pre> -> ```text```\n markdown = markdown.replace(/<pre>([^<]+)<\\/pre>/gi, \"```\\n$1\\n```\");\n\n // Strip remaining HTML tags\n markdown = markdown.replace(/<[^>]+>/g, \"\");\n\n // Decode HTML entities\n markdown = markdown\n .replace(/&lt;/g, \"<\")\n .replace(/&gt;/g, \">\")\n .replace(/&amp;/g, \"&\")\n .replace(/&quot;/g, '\"')\n .replace(/&#39;/g, \"'\");\n\n return parseMarkdown(markdown);\n }\n\n private nodeToTeams(node: Content): string {\n switch (node.type) {\n case \"paragraph\":\n return (node as Paragraph).children\n .map((child) => this.nodeToTeams(child as Content))\n .join(\"\");\n\n case \"text\": {\n // Convert @mentions to Teams format <at>mention</at>\n const textValue = (node as Text).value;\n return textValue.replace(/@(\\w+)/g, \"<at>$1</at>\");\n }\n\n case \"strong\":\n // Teams supports **text** markdown\n return `**${(node as Strong).children\n .map((child) => this.nodeToTeams(child as Content))\n .join(\"\")}**`;\n\n case \"emphasis\":\n // Teams supports _text_ markdown\n return `_${(node as Emphasis).children\n .map((child) => this.nodeToTeams(child as Content))\n .join(\"\")}_`;\n\n case \"delete\":\n // Teams supports ~~text~~ markdown\n return `~~${(node as Delete).children\n .map((child) => this.nodeToTeams(child as Content))\n .join(\"\")}~~`;\n\n case \"inlineCode\":\n return `\\`${(node as InlineCode).value}\\``;\n\n case \"code\": {\n const codeNode = node as Code;\n return `\\`\\`\\`${codeNode.lang || \"\"}\\n${codeNode.value}\\n\\`\\`\\``;\n }\n\n case \"link\": {\n const linkNode = node as Link;\n const linkText = linkNode.children\n .map((child) => this.nodeToTeams(child as Content))\n .join(\"\");\n // Standard markdown link format\n return `[${linkText}](${linkNode.url})`;\n }\n\n case \"blockquote\":\n return node.children\n .map((child) => `> ${this.nodeToTeams(child as Content)}`)\n .join(\"\\n\");\n\n case \"list\":\n return node.children\n .map((item, i) => {\n const prefix = node.ordered ? `${i + 1}.` : \"-\";\n const content = item.children\n .map((child) => this.nodeToTeams(child as Content))\n .join(\"\");\n return `${prefix} ${content}`;\n })\n .join(\"\\n\");\n\n case \"listItem\":\n return node.children\n .map((child) => this.nodeToTeams(child as Content))\n .join(\"\");\n\n case \"break\":\n return \"\\n\";\n\n case \"thematicBreak\":\n return \"---\";\n\n default:\n // For unsupported nodes, try to extract text\n if (\"children\" in node && Array.isArray(node.children)) {\n return node.children\n .map((child) => this.nodeToTeams(child as Content))\n .join(\"\");\n }\n if (\"value\" in node) {\n return String(node.value);\n }\n return \"\";\n }\n }\n}\n"],"mappings":"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","names":["convertEmojiPlaceholders","convertEmojiPlaceholders"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/cards.ts","../src/markdown.ts"],"sourcesContent":["import { ClientSecretCredential } from \"@azure/identity\";\nimport { Client } from \"@microsoft/microsoft-graph-client\";\nimport {\n TokenCredentialAuthenticationProvider,\n type TokenCredentialAuthenticationProviderOptions,\n} from \"@microsoft/microsoft-graph-client/authProviders/azureTokenCredentials\";\nimport type { Activity, ConversationReference } from \"botbuilder\";\nimport {\n ActivityTypes,\n CloudAdapter,\n ConfigurationBotFrameworkAuthentication,\n TeamsInfo,\n type TurnContext,\n} from \"botbuilder\";\n\n/** Extended CloudAdapter that exposes processActivity for serverless environments */\nclass ServerlessCloudAdapter extends CloudAdapter {\n handleActivity(\n authHeader: string,\n activity: Activity,\n logic: (context: TurnContext) => Promise<void>,\n ) {\n return this.processActivity(authHeader, activity, logic);\n }\n}\n\nimport type {\n ActionEvent,\n Adapter,\n AdapterPostableMessage,\n Attachment,\n ChatInstance,\n EmojiValue,\n FetchOptions,\n FetchResult,\n FileUpload,\n FormattedContent,\n Logger,\n Message,\n RawMessage,\n ReactionEvent,\n ThreadInfo,\n WebhookOptions,\n} from \"chat\";\nimport {\n convertEmojiPlaceholders,\n defaultEmojiResolver,\n isCardElement,\n NotImplementedError,\n} from \"chat\";\nimport { cardToAdaptiveCard } from \"./cards\";\nimport { TeamsFormatConverter } from \"./markdown\";\n\n/** Microsoft Graph API chat message type */\ninterface GraphChatMessage {\n id: string;\n createdDateTime?: string;\n lastModifiedDateTime?: string;\n replyToId?: string; // ID of parent message for channel threads\n body?: {\n content?: string;\n contentType?: \"text\" | \"html\";\n };\n from?: {\n user?: {\n id?: string;\n displayName?: string;\n };\n application?: {\n id?: string;\n displayName?: string;\n };\n };\n attachments?: Array<{\n id?: string;\n contentType?: string;\n contentUrl?: string;\n content?: string; // JSON string for adaptive cards\n name?: string;\n }>;\n}\n\nexport interface TeamsAdapterConfig {\n /** Microsoft App ID */\n appId: string;\n /** Microsoft App Password */\n appPassword: string;\n /** Microsoft App Type */\n appType?: \"MultiTenant\" | \"SingleTenant\";\n /** Microsoft App Tenant ID */\n appTenantId?: string;\n /** Override bot username (optional) */\n userName?: string;\n}\n\n/** Teams-specific thread ID data */\nexport interface TeamsThreadId {\n conversationId: string;\n serviceUrl: string;\n replyToId?: string;\n}\n\n/** Teams channel context extracted from activity.channelData */\ninterface TeamsChannelContext {\n teamId: string;\n channelId: string;\n tenantId: string;\n}\n\nexport class TeamsAdapter implements Adapter<TeamsThreadId, unknown> {\n readonly name = \"teams\";\n readonly userName: string;\n readonly botUserId?: string;\n\n private botAdapter: ServerlessCloudAdapter;\n private graphClient: Client | null = null;\n private chat: ChatInstance | null = null;\n private logger: Logger | null = null;\n private formatConverter = new TeamsFormatConverter();\n private config: TeamsAdapterConfig;\n\n constructor(config: TeamsAdapterConfig) {\n this.config = config;\n this.userName = config.userName || \"bot\";\n\n if (config.appType === \"SingleTenant\" && !config.appTenantId) {\n throw new Error(\"appTenantId is required for SingleTenant app type\");\n }\n\n // Pass empty config object, credentials go via factory\n const auth = new ConfigurationBotFrameworkAuthentication({\n MicrosoftAppId: config.appId,\n MicrosoftAppPassword: config.appPassword,\n MicrosoftAppType: config.appType || \"MultiTenant\",\n MicrosoftAppTenantId:\n config.appType === \"SingleTenant\" ? config.appTenantId : undefined,\n });\n\n this.botAdapter = new ServerlessCloudAdapter(auth);\n\n // Initialize Microsoft Graph client for message history (requires tenant ID)\n if (config.appTenantId) {\n const credential = new ClientSecretCredential(\n config.appTenantId,\n config.appId,\n config.appPassword,\n );\n\n const authProvider = new TokenCredentialAuthenticationProvider(\n credential,\n {\n scopes: [\"https://graph.microsoft.com/.default\"],\n } as TokenCredentialAuthenticationProviderOptions,\n );\n\n this.graphClient = Client.initWithMiddleware({ authProvider });\n }\n }\n\n async initialize(chat: ChatInstance): Promise<void> {\n this.chat = chat;\n this.logger = chat.getLogger(this.name);\n }\n\n async handleWebhook(\n request: Request,\n options?: WebhookOptions,\n ): Promise<Response> {\n const body = await request.text();\n this.logger?.debug(\"Teams webhook raw body\", { body });\n\n let activity: Activity;\n try {\n activity = JSON.parse(body);\n } catch (e) {\n this.logger?.error(\"Failed to parse request body\", { error: e });\n return new Response(\"Invalid JSON\", { status: 400 });\n }\n\n // Get the auth header for token validation\n const authHeader = request.headers.get(\"authorization\") || \"\";\n\n try {\n // Use handleActivity which takes the activity directly\n // instead of mocking Node.js req/res objects\n await this.botAdapter.handleActivity(\n authHeader,\n activity,\n async (context) => {\n await this.handleTurn(context, options);\n },\n );\n\n return new Response(JSON.stringify({}), {\n status: 200,\n headers: { \"Content-Type\": \"application/json\" },\n });\n } catch (error) {\n this.logger?.error(\"Bot adapter process error\", { error });\n return new Response(JSON.stringify({ error: \"Internal error\" }), {\n status: 500,\n headers: { \"Content-Type\": \"application/json\" },\n });\n }\n }\n\n private async handleTurn(\n context: TurnContext,\n options?: WebhookOptions,\n ): Promise<void> {\n if (!this.chat) {\n this.logger?.warn(\"Chat instance not initialized, ignoring event\");\n return;\n }\n\n const activity = context.activity;\n\n // Cache serviceUrl and tenantId for the user - needed for opening DMs later\n if (activity.from?.id && activity.serviceUrl) {\n const userId = activity.from.id;\n const channelData = activity.channelData as {\n tenant?: { id?: string };\n team?: { id?: string };\n channel?: { id?: string };\n };\n const tenantId = channelData?.tenant?.id;\n const ttl = 30 * 24 * 60 * 60 * 1000; // 30 days\n\n // Store serviceUrl and tenantId for DM creation\n this.chat\n .getState()\n .set(`teams:serviceUrl:${userId}`, activity.serviceUrl, ttl);\n if (tenantId) {\n this.chat.getState().set(`teams:tenantId:${userId}`, tenantId, ttl);\n }\n\n // Cache team/channel context for proper message fetching in channel threads\n // This allows fetchMessages to use the channel-specific endpoint for thread filtering\n // The Graph API requires aadGroupId (GUID format), not the Teams thread-style ID\n // Note: The botbuilder types don't include aadGroupId, but it's present at runtime\n // aadGroupId is only available in installationUpdate/conversationUpdate events\n const team = channelData?.team as\n | { id?: string; aadGroupId?: string }\n | undefined;\n const teamAadGroupId = team?.aadGroupId;\n const teamThreadId = team?.id; // Thread-style ID like \"19:xxx@thread.tacv2\"\n const conversationId = activity.conversation?.id || \"\";\n const baseChannelId = conversationId.replace(/;messageid=\\d+/, \"\");\n\n if (teamAadGroupId && channelData?.channel?.id && tenantId) {\n // We have aadGroupId (from installationUpdate/conversationUpdate) - cache it\n const context: TeamsChannelContext = {\n teamId: teamAadGroupId, // Use aadGroupId (GUID) for Graph API\n channelId: channelData.channel.id,\n tenantId,\n };\n const contextJson = JSON.stringify(context);\n\n // Cache by conversation ID (channel)\n this.chat\n .getState()\n .set(`teams:channelContext:${baseChannelId}`, contextJson, ttl);\n\n // Also cache by team thread-style ID for lookup from regular messages\n // (which don't have aadGroupId but do have team.id)\n if (teamThreadId) {\n this.chat\n .getState()\n .set(`teams:teamContext:${teamThreadId}`, contextJson, ttl);\n }\n\n this.logger?.info(\n \"Cached Teams team GUID from installation/update event\",\n {\n activityType: activity.type,\n conversationId: baseChannelId,\n teamThreadId,\n teamGuid: context.teamId,\n channelId: context.channelId,\n },\n );\n } else if (teamThreadId && channelData?.channel?.id && tenantId) {\n // Regular message event - no aadGroupId, but try to look up from previous cache\n const cachedTeamContext = await this.chat\n .getState()\n .get<string>(`teams:teamContext:${teamThreadId}`);\n\n if (cachedTeamContext) {\n // Found cached context from installation event - also cache by channel ID\n this.chat\n .getState()\n .set(\n `teams:channelContext:${baseChannelId}`,\n cachedTeamContext,\n ttl,\n );\n this.logger?.info(\"Using cached Teams team GUID for channel\", {\n conversationId: baseChannelId,\n teamThreadId,\n });\n } else {\n // No cached context - try to fetch team details via Bot Framework API\n // TeamsInfo.getTeamDetails() calls /v3/teams/{teamId} and returns aadGroupId\n try {\n const teamDetails = await TeamsInfo.getTeamDetails(context);\n if (teamDetails?.aadGroupId) {\n const fetchedContext: TeamsChannelContext = {\n teamId: teamDetails.aadGroupId,\n channelId: channelData.channel.id,\n tenantId,\n };\n const contextJson = JSON.stringify(fetchedContext);\n\n // Cache by conversation ID\n this.chat\n .getState()\n .set(`teams:channelContext:${baseChannelId}`, contextJson, ttl);\n\n // Also cache by team thread-style ID\n this.chat\n .getState()\n .set(`teams:teamContext:${teamThreadId}`, contextJson, ttl);\n\n this.logger?.info(\n \"Fetched and cached Teams team GUID via TeamsInfo API\",\n {\n conversationId: baseChannelId,\n teamThreadId,\n teamGuid: teamDetails.aadGroupId,\n teamName: teamDetails.name,\n },\n );\n }\n } catch (error) {\n // TeamsInfo.getTeamDetails() only works in team scope\n this.logger?.debug(\n \"Could not fetch team details (may not be a team scope)\",\n { teamThreadId, error },\n );\n }\n }\n }\n }\n\n // Handle message reactions\n if (activity.type === ActivityTypes.MessageReaction) {\n this.handleReactionActivity(activity, options);\n return;\n }\n\n // Handle adaptive card actions (button clicks)\n if (activity.type === ActivityTypes.Invoke) {\n await this.handleInvokeActivity(context, options);\n return;\n }\n\n // Only handle message activities\n if (activity.type !== ActivityTypes.Message) {\n this.logger?.debug(\"Ignoring non-message activity\", {\n type: activity.type,\n });\n return;\n }\n\n // Check if this message activity is actually a button click (Action.Submit)\n // Teams sends Action.Submit as a message with value.actionId\n const actionValue = activity.value as\n | { actionId?: string; value?: string }\n | undefined;\n if (actionValue?.actionId) {\n this.handleMessageAction(activity, actionValue, options);\n return;\n }\n\n const threadId = this.encodeThreadId({\n conversationId: activity.conversation?.id || \"\",\n serviceUrl: activity.serviceUrl || \"\",\n replyToId: activity.replyToId,\n });\n\n // Let Chat class handle async processing and waitUntil\n this.chat.processMessage(\n this,\n threadId,\n this.parseTeamsMessage(activity, threadId),\n options,\n );\n }\n\n /**\n * Handle Action.Submit button clicks sent as message activities.\n * Teams sends these with type \"message\" and value.actionId.\n */\n private handleMessageAction(\n activity: Activity,\n actionValue: { actionId?: string; value?: string },\n options?: WebhookOptions,\n ): void {\n if (!this.chat || !actionValue.actionId) return;\n\n const threadId = this.encodeThreadId({\n conversationId: activity.conversation?.id || \"\",\n serviceUrl: activity.serviceUrl || \"\",\n });\n\n const actionEvent: Omit<ActionEvent, \"thread\"> & { adapter: TeamsAdapter } =\n {\n actionId: actionValue.actionId,\n value: actionValue.value,\n user: {\n userId: activity.from?.id || \"unknown\",\n userName: activity.from?.name || \"unknown\",\n fullName: activity.from?.name || \"unknown\",\n isBot: false,\n isMe: false,\n },\n messageId: activity.replyToId || activity.id || \"\",\n threadId,\n adapter: this,\n raw: activity,\n };\n\n this.logger?.debug(\"Processing Teams message action (Action.Submit)\", {\n actionId: actionValue.actionId,\n value: actionValue.value,\n messageId: actionEvent.messageId,\n threadId,\n });\n\n this.chat.processAction(actionEvent, options);\n }\n\n /**\n * Handle invoke activities (adaptive card actions, etc.).\n */\n private async handleInvokeActivity(\n context: TurnContext,\n options?: WebhookOptions,\n ): Promise<void> {\n const activity = context.activity;\n\n // Handle adaptive card action invokes\n if (activity.name === \"adaptiveCard/action\") {\n await this.handleAdaptiveCardAction(context, activity, options);\n return;\n }\n\n this.logger?.debug(\"Ignoring unsupported invoke\", {\n name: activity.name,\n });\n }\n\n /**\n * Handle adaptive card button clicks.\n * The action data is in activity.value with our { actionId, value } structure.\n */\n private async handleAdaptiveCardAction(\n context: TurnContext,\n activity: Activity,\n options?: WebhookOptions,\n ): Promise<void> {\n if (!this.chat) return;\n\n // Activity.value contains our action data\n const actionData = activity.value?.action?.data as\n | { actionId?: string; value?: string }\n | undefined;\n\n if (!actionData?.actionId) {\n this.logger?.debug(\"Adaptive card action missing actionId\", {\n value: activity.value,\n });\n // Send acknowledgment response\n await context.sendActivity({\n type: ActivityTypes.InvokeResponse,\n value: { status: 200 },\n });\n return;\n }\n\n const threadId = this.encodeThreadId({\n conversationId: activity.conversation?.id || \"\",\n serviceUrl: activity.serviceUrl || \"\",\n });\n\n const actionEvent: Omit<ActionEvent, \"thread\"> & { adapter: TeamsAdapter } =\n {\n actionId: actionData.actionId,\n value: actionData.value,\n user: {\n userId: activity.from?.id || \"unknown\",\n userName: activity.from?.name || \"unknown\",\n fullName: activity.from?.name || \"unknown\",\n isBot: false,\n isMe: false,\n },\n messageId: activity.replyToId || activity.id || \"\",\n threadId,\n adapter: this,\n raw: activity,\n };\n\n this.logger?.debug(\"Processing Teams adaptive card action\", {\n actionId: actionData.actionId,\n value: actionData.value,\n messageId: actionEvent.messageId,\n threadId,\n });\n\n this.chat.processAction(actionEvent, options);\n\n // Send acknowledgment response to prevent timeout\n await context.sendActivity({\n type: ActivityTypes.InvokeResponse,\n value: { status: 200 },\n });\n }\n\n /**\n * Handle Teams reaction events (reactionsAdded/reactionsRemoved).\n */\n private handleReactionActivity(\n activity: Activity,\n options?: WebhookOptions,\n ): void {\n if (!this.chat) return;\n\n // Extract the message ID from conversation ID\n // Format: \"19:xxx@thread.tacv2;messageid=1767297849909\"\n const conversationId = activity.conversation?.id || \"\";\n const messageIdMatch = conversationId.match(/messageid=(\\d+)/);\n const messageId = messageIdMatch?.[1] || activity.replyToId || \"\";\n\n // Build thread ID - KEEP the full conversation ID including ;messageid=XXX\n // This is required for Teams to reply in the correct thread\n const threadId = this.encodeThreadId({\n conversationId: conversationId,\n serviceUrl: activity.serviceUrl || \"\",\n });\n\n const user = {\n userId: activity.from?.id || \"unknown\",\n userName: activity.from?.name || \"unknown\",\n fullName: activity.from?.name,\n isBot: false,\n isMe: this.isMessageFromSelf(activity),\n };\n\n // Process added reactions\n const reactionsAdded = activity.reactionsAdded || [];\n for (const reaction of reactionsAdded) {\n const rawEmoji = reaction.type || \"\";\n const emojiValue = defaultEmojiResolver.fromTeams(rawEmoji);\n\n const event: Omit<ReactionEvent, \"adapter\" | \"thread\"> = {\n emoji: emojiValue,\n rawEmoji,\n added: true,\n user,\n messageId,\n threadId,\n raw: activity,\n };\n\n this.logger?.debug(\"Processing Teams reaction added\", {\n emoji: emojiValue.name,\n rawEmoji,\n messageId,\n });\n\n this.chat.processReaction({ ...event, adapter: this }, options);\n }\n\n // Process removed reactions\n const reactionsRemoved = activity.reactionsRemoved || [];\n for (const reaction of reactionsRemoved) {\n const rawEmoji = reaction.type || \"\";\n const emojiValue = defaultEmojiResolver.fromTeams(rawEmoji);\n\n const event: Omit<ReactionEvent, \"adapter\" | \"thread\"> = {\n emoji: emojiValue,\n rawEmoji,\n added: false,\n user,\n messageId,\n threadId,\n raw: activity,\n };\n\n this.logger?.debug(\"Processing Teams reaction removed\", {\n emoji: emojiValue.name,\n rawEmoji,\n messageId,\n });\n\n this.chat.processReaction({ ...event, adapter: this }, options);\n }\n }\n\n private parseTeamsMessage(\n activity: Activity,\n threadId: string,\n ): Message<unknown> {\n const text = activity.text || \"\";\n // Normalize mentions - format converter will convert <at>name</at> to @name\n const normalizedText = this.normalizeMentions(text, activity);\n\n const isMe = this.isMessageFromSelf(activity);\n\n return {\n id: activity.id || \"\",\n threadId,\n text: this.formatConverter.extractPlainText(normalizedText),\n formatted: this.formatConverter.toAst(normalizedText),\n raw: activity,\n author: {\n userId: activity.from?.id || \"unknown\",\n userName: activity.from?.name || \"unknown\",\n fullName: activity.from?.name || \"unknown\",\n isBot: activity.from?.role === \"bot\",\n isMe,\n },\n metadata: {\n dateSent: activity.timestamp\n ? new Date(activity.timestamp)\n : new Date(),\n edited: false,\n },\n attachments: (activity.attachments || [])\n .filter(\n (att) =>\n // Filter out adaptive cards (handled separately as cards, not attachments)\n att.contentType !== \"application/vnd.microsoft.card.adaptive\" &&\n // Filter out text/html without contentUrl - this is just the formatted\n // version of the message text, not an actual file attachment.\n // Real HTML file attachments would have a contentUrl.\n !(att.contentType === \"text/html\" && !att.contentUrl),\n )\n .map((att) => this.createAttachment(att)),\n };\n }\n\n /**\n * Create an Attachment object from a Teams attachment.\n */\n private createAttachment(att: {\n contentType?: string;\n contentUrl?: string;\n name?: string;\n }): Attachment {\n const url = att.contentUrl;\n\n // Determine type based on contentType\n let type: Attachment[\"type\"] = \"file\";\n if (att.contentType?.startsWith(\"image/\")) {\n type = \"image\";\n } else if (att.contentType?.startsWith(\"video/\")) {\n type = \"video\";\n } else if (att.contentType?.startsWith(\"audio/\")) {\n type = \"audio\";\n }\n\n return {\n type,\n url,\n name: att.name,\n mimeType: att.contentType,\n fetchData: url\n ? async () => {\n const response = await fetch(url);\n if (!response.ok) {\n throw new Error(\n `Failed to fetch file: ${response.status} ${response.statusText}`,\n );\n }\n const arrayBuffer = await response.arrayBuffer();\n return Buffer.from(arrayBuffer);\n }\n : undefined,\n };\n }\n\n private normalizeMentions(text: string, _activity: Activity): string {\n // Don't strip mentions - the format converter will convert <at>name</at> to @name\n // Just trim any leading/trailing whitespace that might result from mention placement\n return text.trim();\n }\n\n async postMessage(\n threadId: string,\n message: AdapterPostableMessage,\n ): Promise<RawMessage<unknown>> {\n const { conversationId, serviceUrl } = this.decodeThreadId(threadId);\n\n // Check for files to upload\n const files = this.extractFiles(message);\n const fileAttachments =\n files.length > 0 ? await this.filesToAttachments(files) : [];\n\n // Check if message contains a card\n const card = this.extractCard(message);\n let activity: Partial<Activity>;\n\n if (card) {\n // Render card as Adaptive Card\n const adaptiveCard = cardToAdaptiveCard(card);\n\n activity = {\n type: ActivityTypes.Message,\n // Don't include text - Teams shows both text and card if text is present\n attachments: [\n {\n contentType: \"application/vnd.microsoft.card.adaptive\",\n content: adaptiveCard,\n },\n ...fileAttachments,\n ],\n };\n\n this.logger?.debug(\"Teams API: sendActivity (adaptive card)\", {\n conversationId,\n serviceUrl,\n fileCount: fileAttachments.length,\n });\n } else {\n // Regular text message\n const text = convertEmojiPlaceholders(\n this.formatConverter.renderPostable(message),\n \"teams\",\n );\n\n activity = {\n type: ActivityTypes.Message,\n text,\n textFormat: \"markdown\",\n attachments: fileAttachments.length > 0 ? fileAttachments : undefined,\n };\n\n this.logger?.debug(\"Teams API: sendActivity (message)\", {\n conversationId,\n serviceUrl,\n textLength: text.length,\n fileCount: fileAttachments.length,\n });\n }\n\n // Use the adapter to send the message\n const conversationReference = {\n channelId: \"msteams\",\n serviceUrl,\n conversation: { id: conversationId },\n };\n\n let messageId = \"\";\n\n await this.botAdapter.continueConversationAsync(\n this.config.appId,\n conversationReference as Partial<ConversationReference>,\n async (context) => {\n const response = await context.sendActivity(activity);\n messageId = response?.id || \"\";\n },\n );\n\n this.logger?.debug(\"Teams API: sendActivity response\", { messageId });\n\n return {\n id: messageId,\n threadId,\n raw: activity,\n };\n }\n\n /**\n * Extract card element from a AdapterPostableMessage if present.\n */\n private extractCard(\n message: AdapterPostableMessage,\n ): import(\"chat\").CardElement | null {\n if (isCardElement(message)) {\n return message;\n }\n if (typeof message === \"object\" && message !== null && \"card\" in message) {\n return message.card;\n }\n return null;\n }\n\n /**\n * Extract files from a AdapterPostableMessage if present.\n */\n private extractFiles(message: AdapterPostableMessage): FileUpload[] {\n if (typeof message === \"object\" && message !== null && \"files\" in message) {\n return (message as { files?: FileUpload[] }).files ?? [];\n }\n return [];\n }\n\n /**\n * Convert files to Teams attachments.\n * Uses inline data URIs for small files.\n */\n private async filesToAttachments(\n files: FileUpload[],\n ): Promise<Array<{ contentType: string; contentUrl: string; name: string }>> {\n const attachments: Array<{\n contentType: string;\n contentUrl: string;\n name: string;\n }> = [];\n\n for (const file of files) {\n // Convert data to Buffer\n let buffer: Buffer;\n if (Buffer.isBuffer(file.data)) {\n buffer = file.data;\n } else if (file.data instanceof ArrayBuffer) {\n buffer = Buffer.from(file.data);\n } else if (file.data instanceof Blob) {\n const arrayBuffer = await file.data.arrayBuffer();\n buffer = Buffer.from(arrayBuffer);\n } else {\n continue;\n }\n\n // Create data URI\n const mimeType = file.mimeType || \"application/octet-stream\";\n const base64 = buffer.toString(\"base64\");\n const dataUri = `data:${mimeType};base64,${base64}`;\n\n attachments.push({\n contentType: mimeType,\n contentUrl: dataUri,\n name: file.filename,\n });\n }\n\n return attachments;\n }\n\n async editMessage(\n threadId: string,\n messageId: string,\n message: AdapterPostableMessage,\n ): Promise<RawMessage<unknown>> {\n const { conversationId, serviceUrl } = this.decodeThreadId(threadId);\n\n // Check if message contains a card\n const card = this.extractCard(message);\n let activity: Partial<Activity>;\n\n if (card) {\n // Render card as Adaptive Card\n const adaptiveCard = cardToAdaptiveCard(card);\n\n activity = {\n id: messageId,\n type: ActivityTypes.Message,\n // Don't include text - Teams shows both text and card if text is present\n attachments: [\n {\n contentType: \"application/vnd.microsoft.card.adaptive\",\n content: adaptiveCard,\n },\n ],\n };\n\n this.logger?.debug(\"Teams API: updateActivity (adaptive card)\", {\n conversationId,\n messageId,\n });\n } else {\n // Regular text message\n const text = convertEmojiPlaceholders(\n this.formatConverter.renderPostable(message),\n \"teams\",\n );\n\n activity = {\n id: messageId,\n type: ActivityTypes.Message,\n text,\n textFormat: \"markdown\",\n };\n\n this.logger?.debug(\"Teams API: updateActivity\", {\n conversationId,\n messageId,\n textLength: text.length,\n });\n }\n\n const conversationReference = {\n channelId: \"msteams\",\n serviceUrl,\n conversation: { id: conversationId },\n };\n\n await this.botAdapter.continueConversationAsync(\n this.config.appId,\n conversationReference as Partial<ConversationReference>,\n async (context) => {\n await context.updateActivity(activity);\n },\n );\n\n this.logger?.debug(\"Teams API: updateActivity response\", { ok: true });\n\n return {\n id: messageId,\n threadId,\n raw: activity,\n };\n }\n\n async deleteMessage(threadId: string, messageId: string): Promise<void> {\n const { conversationId, serviceUrl } = this.decodeThreadId(threadId);\n\n const conversationReference = {\n channelId: \"msteams\",\n serviceUrl,\n conversation: { id: conversationId },\n };\n\n this.logger?.debug(\"Teams API: deleteActivity\", {\n conversationId,\n messageId,\n });\n\n await this.botAdapter.continueConversationAsync(\n this.config.appId,\n conversationReference as Partial<ConversationReference>,\n async (context) => {\n await context.deleteActivity(messageId);\n },\n );\n\n this.logger?.debug(\"Teams API: deleteActivity response\", { ok: true });\n }\n\n async addReaction(\n _threadId: string,\n _messageId: string,\n _emoji: EmojiValue | string,\n ): Promise<void> {\n throw new NotImplementedError(\n \"Teams Bot Framework does not expose reaction APIs\",\n \"addReaction\",\n );\n }\n\n async removeReaction(\n _threadId: string,\n _messageId: string,\n _emoji: EmojiValue | string,\n ): Promise<void> {\n throw new NotImplementedError(\n \"Teams Bot Framework does not expose reaction APIs\",\n \"removeReaction\",\n );\n }\n\n async startTyping(threadId: string): Promise<void> {\n const { conversationId, serviceUrl } = this.decodeThreadId(threadId);\n\n const conversationReference = {\n channelId: \"msteams\",\n serviceUrl,\n conversation: { id: conversationId },\n };\n\n this.logger?.debug(\"Teams API: sendActivity (typing)\", { conversationId });\n\n await this.botAdapter.continueConversationAsync(\n this.config.appId,\n conversationReference as Partial<ConversationReference>,\n async (context) => {\n await context.sendActivity({ type: ActivityTypes.Typing });\n },\n );\n\n this.logger?.debug(\"Teams API: sendActivity (typing) response\", {\n ok: true,\n });\n }\n\n /**\n * Open a direct message conversation with a user.\n * Returns a thread ID that can be used to post messages.\n *\n * The serviceUrl and tenantId are automatically resolved from cached user interactions.\n * If no cached values are found, defaults are used (which may not work for all tenants).\n */\n async openDM(userId: string): Promise<string> {\n // Look up cached serviceUrl and tenantId for this user from state\n const cachedServiceUrl = await this.chat\n ?.getState()\n .get<string>(`teams:serviceUrl:${userId}`);\n const cachedTenantId = await this.chat\n ?.getState()\n .get<string>(`teams:tenantId:${userId}`);\n\n const serviceUrl =\n cachedServiceUrl || \"https://smba.trafficmanager.net/teams/\";\n // Use cached tenant ID, config tenant ID, or undefined (will fail for multi-tenant)\n const tenantId = cachedTenantId || this.config.appTenantId;\n\n this.logger?.debug(\"Teams: creating 1:1 conversation\", {\n userId,\n serviceUrl,\n tenantId,\n cachedServiceUrl: !!cachedServiceUrl,\n cachedTenantId: !!cachedTenantId,\n });\n\n if (!tenantId) {\n throw new Error(\n \"Cannot open DM: tenant ID not found. User must interact with the bot first (via @mention) to cache their tenant ID.\",\n );\n }\n\n let conversationId = \"\";\n\n // Create the 1:1 conversation using createConversationAsync\n // The conversation ID is captured from within the callback, not from the return value\n // biome-ignore lint/suspicious/noExplicitAny: BotBuilder types are incomplete\n await (this.botAdapter as any).createConversationAsync(\n this.config.appId,\n \"msteams\",\n serviceUrl,\n \"\", // empty audience\n {\n isGroup: false,\n bot: { id: this.config.appId, name: this.userName },\n members: [{ id: userId }],\n tenantId,\n channelData: {\n tenant: { id: tenantId },\n },\n },\n async (turnContext: TurnContext) => {\n // Capture the conversation ID from the new context\n conversationId = turnContext?.activity?.conversation?.id || \"\";\n this.logger?.debug(\"Teams: conversation created in callback\", {\n conversationId,\n activityId: turnContext?.activity?.id,\n });\n },\n );\n\n if (!conversationId) {\n throw new Error(\"Failed to create 1:1 conversation - no ID returned\");\n }\n\n this.logger?.debug(\"Teams: 1:1 conversation created\", { conversationId });\n\n return this.encodeThreadId({\n conversationId,\n serviceUrl,\n });\n }\n\n async fetchMessages(\n threadId: string,\n options: FetchOptions = {},\n ): Promise<FetchResult<unknown>> {\n if (!this.graphClient) {\n throw new NotImplementedError(\n \"Teams fetchMessages requires appTenantId to be configured for Microsoft Graph API access.\",\n \"fetchMessages\",\n );\n }\n\n const { conversationId } = this.decodeThreadId(threadId);\n const limit = options.limit || 50;\n const cursor = options.cursor;\n const direction = options.direction ?? \"backward\";\n\n // Extract message ID for thread filtering (format: \"19:xxx@thread.tacv2;messageid=123456\")\n const messageIdMatch = conversationId.match(/;messageid=(\\d+)/);\n const threadMessageId = messageIdMatch?.[1];\n\n // Strip ;messageid= from conversation ID\n const baseConversationId = conversationId.replace(/;messageid=\\d+/, \"\");\n\n // Try to get cached channel context for proper thread-level message fetching\n let channelContext: TeamsChannelContext | null = null;\n if (threadMessageId && this.chat) {\n const cachedContext = await this.chat\n .getState()\n .get<string>(`teams:channelContext:${baseConversationId}`);\n if (cachedContext) {\n try {\n channelContext = JSON.parse(cachedContext) as TeamsChannelContext;\n } catch {\n // Invalid cached data, ignore\n }\n }\n\n // Note: Team GUID is cached during webhook handling via TeamsInfo.getTeamDetails()\n // If no cached context, we'll fall back to the chat endpoint (less accurate for channels)\n }\n\n try {\n this.logger?.debug(\"Teams Graph API: fetching messages\", {\n conversationId: baseConversationId,\n threadMessageId,\n hasChannelContext: !!channelContext,\n limit,\n cursor,\n direction,\n });\n\n // If we have channel context and a thread message ID, use the channel replies endpoint\n // This gives us proper thread-level filtering instead of all messages in the channel\n if (channelContext && threadMessageId) {\n return this.fetchChannelThreadMessages(\n channelContext,\n threadMessageId,\n threadId,\n options,\n );\n }\n\n // Teams conversation IDs:\n // - Channels: \"19:xxx@thread.tacv2\"\n // - Group chats: \"19:xxx@thread.v2\"\n // - 1:1 chats: other formats (e.g., \"a]xxx\", \"8:orgid:xxx\")\n // For Graph API, we use /chats/{chat-id}/messages for all chat types\n\n // Note: Teams Graph API only supports orderby(\"createdDateTime desc\")\n // Ascending order is not supported, so we work around this limitation.\n // Also, max page size is 50 messages per request.\n\n let graphMessages: GraphChatMessage[];\n let hasMoreMessages = false;\n\n if (direction === \"forward\") {\n // Forward direction: need to fetch ALL messages to find the oldest ones\n // since API only supports descending order. Paginate with max 50 per request.\n const allMessages: GraphChatMessage[] = [];\n let nextLink: string | undefined;\n const apiUrl = `/chats/${encodeURIComponent(baseConversationId)}/messages`;\n\n do {\n const request = nextLink\n ? this.graphClient.api(nextLink)\n : this.graphClient\n .api(apiUrl)\n .top(50) // Max allowed by Teams API\n .orderby(\"createdDateTime desc\");\n\n const response = await request.get();\n const pageMessages = (response.value || []) as GraphChatMessage[];\n allMessages.push(...pageMessages);\n nextLink = response[\"@odata.nextLink\"];\n } while (nextLink);\n\n // Reverse to get chronological order (oldest first)\n allMessages.reverse();\n\n // Find starting position based on cursor (cursor is a timestamp)\n let startIndex = 0;\n if (cursor) {\n startIndex = allMessages.findIndex(\n (msg) => msg.createdDateTime && msg.createdDateTime > cursor,\n );\n if (startIndex === -1) startIndex = allMessages.length;\n }\n\n // Check if there are more messages beyond our slice\n hasMoreMessages = startIndex + limit < allMessages.length;\n // Take only the requested limit\n graphMessages = allMessages.slice(startIndex, startIndex + limit);\n } else {\n // Backward direction: simple pagination\n let request = this.graphClient\n .api(`/chats/${encodeURIComponent(baseConversationId)}/messages`)\n .top(limit)\n .orderby(\"createdDateTime desc\");\n\n if (cursor) {\n // Get messages older than cursor\n request = request.filter(`createdDateTime lt ${cursor}`);\n }\n\n const response = await request.get();\n graphMessages = (response.value || []) as GraphChatMessage[];\n\n // API returns newest first, reverse to get chronological order\n graphMessages.reverse();\n\n // We have more if we got a full page\n hasMoreMessages = graphMessages.length >= limit;\n }\n\n // For group chats (non-channel), filter to only messages from the \"thread\" onwards.\n // Teams group chats don't have real threading - the messageid in the conversation ID\n // is just UI context. We filter by message ID (which is a timestamp) to simulate threading.\n if (threadMessageId && !channelContext) {\n graphMessages = graphMessages.filter((msg) => {\n // Include messages with ID >= thread message ID (IDs are timestamps)\n return msg.id && msg.id >= threadMessageId;\n });\n this.logger?.debug(\"Filtered group chat messages to thread\", {\n threadMessageId,\n filteredCount: graphMessages.length,\n });\n }\n\n this.logger?.debug(\"Teams Graph API: fetched messages\", {\n count: graphMessages.length,\n direction,\n hasMoreMessages,\n });\n\n const messages = graphMessages.map((msg: GraphChatMessage) => {\n const isFromBot =\n msg.from?.application?.id === this.config.appId ||\n msg.from?.user?.id === this.config.appId;\n\n return {\n id: msg.id,\n threadId,\n text: this.extractTextFromGraphMessage(msg),\n formatted: this.formatConverter.toAst(\n this.extractTextFromGraphMessage(msg),\n ),\n raw: msg,\n author: {\n userId:\n msg.from?.user?.id || msg.from?.application?.id || \"unknown\",\n userName:\n msg.from?.user?.displayName ||\n msg.from?.application?.displayName ||\n \"unknown\",\n fullName:\n msg.from?.user?.displayName ||\n msg.from?.application?.displayName ||\n \"unknown\",\n isBot: !!msg.from?.application,\n isMe: isFromBot,\n },\n metadata: {\n dateSent: msg.createdDateTime\n ? new Date(msg.createdDateTime)\n : new Date(),\n edited: !!msg.lastModifiedDateTime,\n },\n attachments: this.extractAttachmentsFromGraphMessage(msg),\n };\n });\n\n // Determine nextCursor based on direction\n let nextCursor: string | undefined;\n if (hasMoreMessages && graphMessages.length > 0) {\n if (direction === \"forward\") {\n // Forward: use the newest message's timestamp (last in returned slice)\n const lastMsg = graphMessages[graphMessages.length - 1];\n if (lastMsg?.createdDateTime) {\n nextCursor = lastMsg.createdDateTime;\n }\n } else {\n // Backward: use the oldest message's timestamp (first in returned array)\n const oldestMsg = graphMessages[0];\n if (oldestMsg?.createdDateTime) {\n nextCursor = oldestMsg.createdDateTime;\n }\n }\n }\n\n return { messages, nextCursor };\n } catch (error) {\n this.logger?.error(\"Teams Graph API: fetchMessages error\", { error });\n\n // Check if it's a permission error\n if (error instanceof Error && error.message?.includes(\"403\")) {\n throw new NotImplementedError(\n \"Teams fetchMessages requires one of these Azure AD app permissions: ChatMessage.Read.Chat, Chat.Read.All, or Chat.Read.WhereInstalled\",\n \"fetchMessages\",\n );\n }\n\n throw error;\n }\n }\n\n /**\n * Fetch messages from a Teams channel thread using the channel-specific Graph API endpoint.\n * This provides proper thread-level filtering by fetching only replies to a specific message.\n *\n * Endpoint: GET /teams/{team-id}/channels/{channel-id}/messages/{message-id}/replies\n */\n private async fetchChannelThreadMessages(\n context: TeamsChannelContext,\n threadMessageId: string,\n threadId: string,\n options: FetchOptions,\n ): Promise<FetchResult<unknown>> {\n const limit = options.limit || 50;\n const cursor = options.cursor;\n const direction = options.direction ?? \"backward\";\n\n this.logger?.debug(\"Teams Graph API: fetching channel thread messages\", {\n teamId: context.teamId,\n channelId: context.channelId,\n threadMessageId,\n limit,\n cursor,\n direction,\n });\n\n // Build the endpoint URLs:\n // Parent message: /teams/{team-id}/channels/{channel-id}/messages/{message-id}\n // Replies: /teams/{team-id}/channels/{channel-id}/messages/{message-id}/replies\n const parentUrl = `/teams/${encodeURIComponent(context.teamId)}/channels/${encodeURIComponent(context.channelId)}/messages/${encodeURIComponent(threadMessageId)}`;\n const repliesUrl = `${parentUrl}/replies`;\n\n const graphClient = this.graphClient;\n if (!graphClient) {\n throw new Error(\"Graph client not initialized\");\n }\n\n // Fetch the parent message (the original message that started the thread)\n let parentMessage: GraphChatMessage | null = null;\n try {\n parentMessage = (await graphClient\n .api(parentUrl)\n .get()) as GraphChatMessage;\n } catch (err) {\n this.logger?.warn(\"Failed to fetch parent message\", {\n threadMessageId,\n err,\n });\n }\n\n let graphMessages: GraphChatMessage[];\n let hasMoreMessages = false;\n\n if (direction === \"forward\") {\n // Forward direction: fetch all replies and paginate in chronological order (oldest first)\n // Graph API returns messages in descending order (newest first), so we must reverse\n const allReplies: GraphChatMessage[] = [];\n let nextLink: string | undefined;\n\n do {\n const request = nextLink\n ? graphClient.api(nextLink)\n : graphClient.api(repliesUrl).top(50);\n\n const response = await request.get();\n const pageMessages = (response.value || []) as GraphChatMessage[];\n allReplies.push(...pageMessages);\n nextLink = response[\"@odata.nextLink\"];\n } while (nextLink);\n\n // Reverse replies to get chronological order (oldest first)\n allReplies.reverse();\n\n // Prepend parent message (it's the oldest - started the thread)\n const allMessages = parentMessage\n ? [parentMessage, ...allReplies]\n : allReplies;\n\n // Find starting position based on cursor\n let startIndex = 0;\n if (cursor) {\n startIndex = allMessages.findIndex(\n (msg) => msg.createdDateTime && msg.createdDateTime > cursor,\n );\n if (startIndex === -1) startIndex = allMessages.length;\n }\n\n hasMoreMessages = startIndex + limit < allMessages.length;\n graphMessages = allMessages.slice(startIndex, startIndex + limit);\n } else {\n // Backward direction: return most recent messages in chronological order\n // Graph API returns messages in descending order (newest first)\n const allReplies: GraphChatMessage[] = [];\n let nextLink: string | undefined;\n\n do {\n const request = nextLink\n ? graphClient.api(nextLink)\n : graphClient.api(repliesUrl).top(50);\n\n const response = await request.get();\n const pageMessages = (response.value || []) as GraphChatMessage[];\n allReplies.push(...pageMessages);\n nextLink = response[\"@odata.nextLink\"];\n } while (nextLink);\n\n // Reverse replies to get chronological order (oldest first)\n allReplies.reverse();\n\n // Prepend parent message (it's the oldest - started the thread)\n const allMessages = parentMessage\n ? [parentMessage, ...allReplies]\n : allReplies;\n\n if (cursor) {\n // Find position of cursor (cursor is timestamp of the oldest message in previous batch)\n // We want messages OLDER than cursor (earlier in chronological order)\n const cursorIndex = allMessages.findIndex(\n (msg) => msg.createdDateTime && msg.createdDateTime >= cursor,\n );\n if (cursorIndex > 0) {\n // Take messages before the cursor position\n const sliceStart = Math.max(0, cursorIndex - limit);\n graphMessages = allMessages.slice(sliceStart, cursorIndex);\n hasMoreMessages = sliceStart > 0;\n } else {\n // Cursor not found or at start - take the most recent (end of array)\n graphMessages = allMessages.slice(-limit);\n hasMoreMessages = allMessages.length > limit;\n }\n } else {\n // No cursor - get the most recent messages (end of chronological array)\n graphMessages = allMessages.slice(-limit);\n hasMoreMessages = allMessages.length > limit;\n }\n }\n\n this.logger?.debug(\"Teams Graph API: fetched channel thread messages\", {\n count: graphMessages.length,\n direction,\n hasMoreMessages,\n });\n\n const messages = graphMessages.map((msg: GraphChatMessage) => {\n const isFromBot =\n msg.from?.application?.id === this.config.appId ||\n msg.from?.user?.id === this.config.appId;\n\n return {\n id: msg.id,\n threadId,\n text: this.extractTextFromGraphMessage(msg),\n formatted: this.formatConverter.toAst(\n this.extractTextFromGraphMessage(msg),\n ),\n raw: msg,\n author: {\n userId: msg.from?.user?.id || msg.from?.application?.id || \"unknown\",\n userName:\n msg.from?.user?.displayName ||\n msg.from?.application?.displayName ||\n \"unknown\",\n fullName:\n msg.from?.user?.displayName ||\n msg.from?.application?.displayName ||\n \"unknown\",\n isBot: !!msg.from?.application,\n isMe: isFromBot,\n },\n metadata: {\n dateSent: msg.createdDateTime\n ? new Date(msg.createdDateTime)\n : new Date(),\n edited: !!msg.lastModifiedDateTime,\n },\n attachments: this.extractAttachmentsFromGraphMessage(msg),\n };\n });\n\n // Determine nextCursor\n let nextCursor: string | undefined;\n if (hasMoreMessages && graphMessages.length > 0) {\n if (direction === \"forward\") {\n const lastMsg = graphMessages[graphMessages.length - 1];\n if (lastMsg?.createdDateTime) {\n nextCursor = lastMsg.createdDateTime;\n }\n } else {\n const oldestMsg = graphMessages[0];\n if (oldestMsg?.createdDateTime) {\n nextCursor = oldestMsg.createdDateTime;\n }\n }\n }\n\n return { messages, nextCursor };\n }\n\n /**\n * Extract plain text from a Graph API message.\n */\n private extractTextFromGraphMessage(msg: GraphChatMessage): string {\n // body.content contains the message text (HTML or text depending on contentType)\n if (msg.body?.contentType === \"text\") {\n return msg.body.content || \"\";\n }\n\n // For HTML content, strip tags (basic implementation)\n let text = \"\";\n if (msg.body?.content) {\n text = msg.body.content.replace(/<[^>]*>/g, \"\").trim();\n }\n\n // If text is empty but message has adaptive card attachments, try to extract card title\n if (!text && msg.attachments?.length) {\n for (const att of msg.attachments) {\n if (att.contentType === \"application/vnd.microsoft.card.adaptive\") {\n try {\n const card = JSON.parse(att.content || \"{}\");\n // Look for title in common locations\n const title = this.extractCardTitle(card);\n if (title) {\n return title;\n }\n return \"[Card]\";\n } catch {\n return \"[Card]\";\n }\n }\n }\n }\n\n return text;\n }\n\n /**\n * Extract a title/summary from an Adaptive Card structure.\n */\n private extractCardTitle(card: unknown): string | null {\n if (!card || typeof card !== \"object\") return null;\n\n const cardObj = card as Record<string, unknown>;\n\n // Check for body array and find first TextBlock with large/bolder style (likely title)\n if (Array.isArray(cardObj.body)) {\n for (const element of cardObj.body) {\n if (\n element &&\n typeof element === \"object\" &&\n (element as Record<string, unknown>).type === \"TextBlock\"\n ) {\n const textBlock = element as Record<string, unknown>;\n // Title blocks often have weight: \"bolder\" or size: \"large\"\n if (\n textBlock.weight === \"bolder\" ||\n textBlock.size === \"large\" ||\n textBlock.size === \"extraLarge\"\n ) {\n const text = textBlock.text;\n if (typeof text === \"string\") {\n return text;\n }\n }\n }\n }\n // Fallback: just get first TextBlock's text\n for (const element of cardObj.body) {\n if (\n element &&\n typeof element === \"object\" &&\n (element as Record<string, unknown>).type === \"TextBlock\"\n ) {\n const text = (element as Record<string, unknown>).text;\n if (typeof text === \"string\") {\n return text;\n }\n }\n }\n }\n\n return null;\n }\n\n /**\n * Extract attachments from a Graph API message.\n */\n private extractAttachmentsFromGraphMessage(\n msg: GraphChatMessage,\n ): Attachment[] {\n if (!msg.attachments?.length) {\n return [];\n }\n\n return msg.attachments.map((att) => ({\n type: att.contentType?.includes(\"image\") ? \"image\" : \"file\",\n name: att.name || undefined,\n url: att.contentUrl || undefined,\n mimeType: att.contentType || undefined,\n }));\n }\n\n async fetchThread(threadId: string): Promise<ThreadInfo> {\n const { conversationId } = this.decodeThreadId(threadId);\n\n return {\n id: threadId,\n channelId: conversationId,\n metadata: {},\n };\n }\n\n encodeThreadId(platformData: TeamsThreadId): string {\n // Base64 encode both since conversationId and serviceUrl can contain special characters\n const encodedConversationId = Buffer.from(\n platformData.conversationId,\n ).toString(\"base64url\");\n const encodedServiceUrl = Buffer.from(platformData.serviceUrl).toString(\n \"base64url\",\n );\n return `teams:${encodedConversationId}:${encodedServiceUrl}`;\n }\n\n /**\n * Check if a thread is a direct message conversation.\n * Teams DMs have conversation IDs that don't start with \"19:\" (which is for groups/channels).\n */\n isDM(threadId: string): boolean {\n const { conversationId } = this.decodeThreadId(threadId);\n // Group chats and channels start with \"19:\", DMs don't\n return !conversationId.startsWith(\"19:\");\n }\n\n decodeThreadId(threadId: string): TeamsThreadId {\n const parts = threadId.split(\":\");\n if (parts.length !== 3 || parts[0] !== \"teams\") {\n throw new Error(`Invalid Teams thread ID: ${threadId}`);\n }\n const conversationId = Buffer.from(\n parts[1] as string,\n \"base64url\",\n ).toString(\"utf-8\");\n const serviceUrl = Buffer.from(parts[2] as string, \"base64url\").toString(\n \"utf-8\",\n );\n return { conversationId, serviceUrl };\n }\n\n parseMessage(raw: unknown): Message<unknown> {\n const activity = raw as Activity;\n const threadId = this.encodeThreadId({\n conversationId: activity.conversation?.id || \"\",\n serviceUrl: activity.serviceUrl || \"\",\n });\n return this.parseTeamsMessage(activity, threadId);\n }\n\n /**\n * Check if a Teams activity is from this bot.\n *\n * Teams bot IDs can appear in different formats:\n * - Just the app ID: \"abc123-def456-...\"\n * - With prefix: \"28:abc123-def456-...\"\n *\n * We check both exact match and suffix match (after colon delimiter)\n * to handle all formats safely.\n */\n private isMessageFromSelf(activity: Activity): boolean {\n const fromId = activity.from?.id;\n if (!fromId || !this.config.appId) {\n return false;\n }\n\n // Exact match (bot ID is just the app ID)\n if (fromId === this.config.appId) {\n return true;\n }\n\n // Teams format: \"28:{appId}\" or similar prefix patterns\n // Check if it ends with our appId after a colon delimiter\n if (fromId.endsWith(`:${this.config.appId}`)) {\n return true;\n }\n\n return false;\n }\n\n renderFormatted(content: FormattedContent): string {\n return this.formatConverter.fromAst(content);\n }\n}\n\nexport function createTeamsAdapter(config: TeamsAdapterConfig): TeamsAdapter {\n return new TeamsAdapter(config);\n}\n\n// Re-export card converter for advanced use\nexport { cardToAdaptiveCard, cardToFallbackText } from \"./cards\";\nexport { TeamsFormatConverter } from \"./markdown\";\n","/**\n * Teams Adaptive Card converter for cross-platform cards.\n *\n * Converts CardElement to Microsoft Adaptive Cards format.\n * @see https://adaptivecards.io/\n */\n\nimport {\n type ActionsElement,\n type ButtonElement,\n type CardChild,\n type CardElement,\n convertEmojiPlaceholders,\n type DividerElement,\n type FieldsElement,\n type ImageElement,\n type SectionElement,\n type TextElement,\n} from \"chat\";\n\n/**\n * Convert emoji placeholders in text to Teams format.\n */\nfunction convertEmoji(text: string): string {\n return convertEmojiPlaceholders(text, \"teams\");\n}\n\n// Adaptive Card types (simplified)\nexport interface AdaptiveCard {\n type: \"AdaptiveCard\";\n $schema: string;\n version: string;\n body: AdaptiveCardElement[];\n actions?: AdaptiveCardAction[];\n}\n\nexport interface AdaptiveCardElement {\n type: string;\n [key: string]: unknown;\n}\n\nexport interface AdaptiveCardAction {\n type: string;\n title: string;\n data?: Record<string, unknown>;\n style?: string;\n}\n\nconst ADAPTIVE_CARD_SCHEMA =\n \"http://adaptivecards.io/schemas/adaptive-card.json\";\nconst ADAPTIVE_CARD_VERSION = \"1.4\";\n\n/**\n * Convert a CardElement to a Teams Adaptive Card.\n */\nexport function cardToAdaptiveCard(card: CardElement): AdaptiveCard {\n const body: AdaptiveCardElement[] = [];\n const actions: AdaptiveCardAction[] = [];\n\n // Add title as TextBlock\n if (card.title) {\n body.push({\n type: \"TextBlock\",\n text: convertEmoji(card.title),\n weight: \"bolder\",\n size: \"large\",\n wrap: true,\n });\n }\n\n // Add subtitle as TextBlock\n if (card.subtitle) {\n body.push({\n type: \"TextBlock\",\n text: convertEmoji(card.subtitle),\n isSubtle: true,\n wrap: true,\n });\n }\n\n // Add header image if present\n if (card.imageUrl) {\n body.push({\n type: \"Image\",\n url: card.imageUrl,\n size: \"stretch\",\n });\n }\n\n // Convert children\n for (const child of card.children) {\n const result = convertChildToAdaptive(child);\n body.push(...result.elements);\n actions.push(...result.actions);\n }\n\n const adaptiveCard: AdaptiveCard = {\n type: \"AdaptiveCard\",\n $schema: ADAPTIVE_CARD_SCHEMA,\n version: ADAPTIVE_CARD_VERSION,\n body,\n };\n\n if (actions.length > 0) {\n adaptiveCard.actions = actions;\n }\n\n return adaptiveCard;\n}\n\ninterface ConvertResult {\n elements: AdaptiveCardElement[];\n actions: AdaptiveCardAction[];\n}\n\n/**\n * Convert a card child element to Adaptive Card elements.\n */\nfunction convertChildToAdaptive(child: CardChild): ConvertResult {\n switch (child.type) {\n case \"text\":\n return { elements: [convertTextToElement(child)], actions: [] };\n case \"image\":\n return { elements: [convertImageToElement(child)], actions: [] };\n case \"divider\":\n return { elements: [convertDividerToElement(child)], actions: [] };\n case \"actions\":\n return convertActionsToElements(child);\n case \"section\":\n return convertSectionToElements(child);\n case \"fields\":\n return { elements: [convertFieldsToElement(child)], actions: [] };\n default:\n return { elements: [], actions: [] };\n }\n}\n\nfunction convertTextToElement(element: TextElement): AdaptiveCardElement {\n const textBlock: AdaptiveCardElement = {\n type: \"TextBlock\",\n text: convertEmoji(element.content),\n wrap: true,\n };\n\n if (element.style === \"bold\") {\n textBlock.weight = \"bolder\";\n } else if (element.style === \"muted\") {\n textBlock.isSubtle = true;\n }\n\n return textBlock;\n}\n\nfunction convertImageToElement(element: ImageElement): AdaptiveCardElement {\n return {\n type: \"Image\",\n url: element.url,\n altText: element.alt || \"Image\",\n size: \"auto\",\n };\n}\n\nfunction convertDividerToElement(\n _element: DividerElement,\n): AdaptiveCardElement {\n // Adaptive Cards don't have a native divider, use a separator container\n return {\n type: \"Container\",\n separator: true,\n items: [],\n };\n}\n\nfunction convertActionsToElements(element: ActionsElement): ConvertResult {\n // In Adaptive Cards, actions go at the card level, not inline\n const actions: AdaptiveCardAction[] = element.children.map((button) =>\n convertButtonToAction(button),\n );\n\n return { elements: [], actions };\n}\n\nfunction convertButtonToAction(button: ButtonElement): AdaptiveCardAction {\n const action: AdaptiveCardAction = {\n type: \"Action.Submit\",\n title: convertEmoji(button.label),\n data: {\n actionId: button.id,\n value: button.value,\n },\n };\n\n if (button.style === \"primary\") {\n action.style = \"positive\";\n } else if (button.style === \"danger\") {\n action.style = \"destructive\";\n }\n\n return action;\n}\n\nfunction convertSectionToElements(element: SectionElement): ConvertResult {\n const elements: AdaptiveCardElement[] = [];\n const actions: AdaptiveCardAction[] = [];\n\n // Wrap section in a container\n const containerItems: AdaptiveCardElement[] = [];\n\n for (const child of element.children) {\n const result = convertChildToAdaptive(child);\n containerItems.push(...result.elements);\n actions.push(...result.actions);\n }\n\n if (containerItems.length > 0) {\n elements.push({\n type: \"Container\",\n items: containerItems,\n });\n }\n\n return { elements, actions };\n}\n\nfunction convertFieldsToElement(element: FieldsElement): AdaptiveCardElement {\n // Use FactSet for key-value pairs\n const facts = element.children.map((field) => ({\n title: convertEmoji(field.label),\n value: convertEmoji(field.value),\n }));\n\n return {\n type: \"FactSet\",\n facts,\n };\n}\n\n/**\n * Generate fallback text from a card element.\n * Used when adaptive cards aren't supported.\n */\nexport function cardToFallbackText(card: CardElement): string {\n const parts: string[] = [];\n\n if (card.title) {\n parts.push(`**${convertEmoji(card.title)}**`);\n }\n\n if (card.subtitle) {\n parts.push(convertEmoji(card.subtitle));\n }\n\n for (const child of card.children) {\n const text = childToFallbackText(child);\n if (text) {\n parts.push(text);\n }\n }\n\n return parts.join(\"\\n\\n\");\n}\n\nfunction childToFallbackText(child: CardChild): string | null {\n switch (child.type) {\n case \"text\":\n return convertEmoji(child.content);\n case \"fields\":\n return child.children\n .map((f) => `**${convertEmoji(f.label)}**: ${convertEmoji(f.value)}`)\n .join(\"\\n\");\n case \"actions\":\n return `[${child.children.map((b) => convertEmoji(b.label)).join(\"] [\")}]`;\n case \"section\":\n return child.children\n .map((c) => childToFallbackText(c))\n .filter(Boolean)\n .join(\"\\n\");\n default:\n return null;\n }\n}\n","/**\n * Teams-specific format conversion using AST-based parsing.\n *\n * Teams supports a subset of HTML for formatting:\n * - Bold: <b> or <strong>\n * - Italic: <i> or <em>\n * - Strikethrough: <s> or <strike>\n * - Links: <a href=\"url\">text</a>\n * - Code: <pre> and <code>\n *\n * Teams also accepts standard markdown in most cases.\n */\n\nimport {\n type AdapterPostableMessage,\n BaseFormatConverter,\n type Code,\n type Content,\n type Delete,\n type Emphasis,\n type InlineCode,\n type Link,\n type Paragraph,\n parseMarkdown,\n type Root,\n type Strong,\n type Text,\n} from \"chat\";\n\nexport class TeamsFormatConverter extends BaseFormatConverter {\n /**\n * Convert @mentions to Teams format in plain text.\n * @name → <at>name</at>\n */\n private convertMentionsToTeams(text: string): string {\n return text.replace(/@(\\w+)/g, \"<at>$1</at>\");\n }\n\n /**\n * Override renderPostable to convert @mentions in plain strings.\n */\n override renderPostable(message: AdapterPostableMessage): string {\n if (typeof message === \"string\") {\n return this.convertMentionsToTeams(message);\n }\n if (\"raw\" in message) {\n return this.convertMentionsToTeams(message.raw);\n }\n if (\"markdown\" in message) {\n return this.fromAst(parseMarkdown(message.markdown));\n }\n if (\"ast\" in message) {\n return this.fromAst(message.ast);\n }\n return \"\";\n }\n\n /**\n * Render an AST to Teams format.\n * Teams accepts standard markdown, so we just stringify cleanly.\n */\n fromAst(ast: Root): string {\n const parts: string[] = [];\n\n for (const node of ast.children) {\n parts.push(this.nodeToTeams(node as Content));\n }\n\n return parts.join(\"\\n\\n\");\n }\n\n /**\n * Parse Teams message into an AST.\n * Converts Teams HTML/mentions to standard markdown format.\n */\n toAst(teamsText: string): Root {\n // Convert Teams HTML to markdown, then parse\n let markdown = teamsText;\n\n // Convert @mentions from Teams format: <at>Name</at> -> @Name\n markdown = markdown.replace(/<at>([^<]+)<\\/at>/gi, \"@$1\");\n\n // Convert HTML tags to markdown\n // Bold: <b>, <strong> -> **text**\n markdown = markdown.replace(\n /<(b|strong)>([^<]+)<\\/(b|strong)>/gi,\n \"**$2**\",\n );\n\n // Italic: <i>, <em> -> _text_\n markdown = markdown.replace(/<(i|em)>([^<]+)<\\/(i|em)>/gi, \"_$2_\");\n\n // Strikethrough: <s>, <strike> -> ~~text~~\n markdown = markdown.replace(\n /<(s|strike)>([^<]+)<\\/(s|strike)>/gi,\n \"~~$2~~\",\n );\n\n // Links: <a href=\"url\">text</a> -> [text](url)\n markdown = markdown.replace(\n /<a[^>]+href=\"([^\"]+)\"[^>]*>([^<]+)<\\/a>/gi,\n \"[$2]($1)\",\n );\n\n // Code: <code>text</code> -> `text`\n markdown = markdown.replace(/<code>([^<]+)<\\/code>/gi, \"`$1`\");\n\n // Pre: <pre>text</pre> -> ```text```\n markdown = markdown.replace(/<pre>([^<]+)<\\/pre>/gi, \"```\\n$1\\n```\");\n\n // Strip remaining HTML tags\n markdown = markdown.replace(/<[^>]+>/g, \"\");\n\n // Decode HTML entities\n markdown = markdown\n .replace(/&lt;/g, \"<\")\n .replace(/&gt;/g, \">\")\n .replace(/&amp;/g, \"&\")\n .replace(/&quot;/g, '\"')\n .replace(/&#39;/g, \"'\");\n\n return parseMarkdown(markdown);\n }\n\n private nodeToTeams(node: Content): string {\n switch (node.type) {\n case \"paragraph\":\n return (node as Paragraph).children\n .map((child) => this.nodeToTeams(child as Content))\n .join(\"\");\n\n case \"text\": {\n // Convert @mentions to Teams format <at>mention</at>\n const textValue = (node as Text).value;\n return textValue.replace(/@(\\w+)/g, \"<at>$1</at>\");\n }\n\n case \"strong\":\n // Teams supports **text** markdown\n return `**${(node as Strong).children\n .map((child) => this.nodeToTeams(child as Content))\n .join(\"\")}**`;\n\n case \"emphasis\":\n // Teams supports _text_ markdown\n return `_${(node as Emphasis).children\n .map((child) => this.nodeToTeams(child as Content))\n .join(\"\")}_`;\n\n case \"delete\":\n // Teams supports ~~text~~ markdown\n return `~~${(node as Delete).children\n .map((child) => this.nodeToTeams(child as Content))\n .join(\"\")}~~`;\n\n case \"inlineCode\":\n return `\\`${(node as InlineCode).value}\\``;\n\n case \"code\": {\n const codeNode = node as Code;\n return `\\`\\`\\`${codeNode.lang || \"\"}\\n${codeNode.value}\\n\\`\\`\\``;\n }\n\n case \"link\": {\n const linkNode = node as Link;\n const linkText = linkNode.children\n .map((child) => this.nodeToTeams(child as Content))\n .join(\"\");\n // Standard markdown link format\n return `[${linkText}](${linkNode.url})`;\n }\n\n case \"blockquote\":\n return node.children\n .map((child) => `> ${this.nodeToTeams(child as Content)}`)\n .join(\"\\n\");\n\n case \"list\":\n return node.children\n .map((item, i) => {\n const prefix = node.ordered ? `${i + 1}.` : \"-\";\n const content = item.children\n .map((child) => this.nodeToTeams(child as Content))\n .join(\"\");\n return `${prefix} ${content}`;\n })\n .join(\"\\n\");\n\n case \"listItem\":\n return node.children\n .map((child) => this.nodeToTeams(child as Content))\n .join(\"\");\n\n case \"break\":\n return \"\\n\";\n\n case \"thematicBreak\":\n return \"---\";\n\n default:\n // For unsupported nodes, try to extract text\n if (\"children\" in node && Array.isArray(node.children)) {\n return node.children\n .map((child) => this.nodeToTeams(child as Content))\n .join(\"\");\n }\n if (\"value\" in node) {\n return String(node.value);\n }\n return \"\";\n }\n }\n}\n"],"mappings":"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","names":["convertEmojiPlaceholders","context","convertEmojiPlaceholders"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chat-adapter/teams",
3
- "version": "4.0.2",
3
+ "version": "4.2.0",
4
4
  "description": "Microsoft Teams adapter for chat",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -16,8 +16,10 @@
16
16
  "dist"
17
17
  ],
18
18
  "dependencies": {
19
+ "@azure/identity": "^4.13.0",
20
+ "@microsoft/microsoft-graph-client": "^3.0.7",
19
21
  "botbuilder": "^4.23.1",
20
- "chat": "4.0.2"
22
+ "chat": "4.2.0"
21
23
  },
22
24
  "devDependencies": {
23
25
  "@types/node": "^22.10.2",