@chat-adapter/teams 4.2.0 → 4.4.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.js CHANGED
@@ -11,19 +11,29 @@ import {
11
11
  TeamsInfo
12
12
  } from "botbuilder";
13
13
  import {
14
- convertEmojiPlaceholders as convertEmojiPlaceholders2,
14
+ AdapterRateLimitError,
15
+ AuthenticationError,
16
+ bufferToDataUri,
17
+ extractCard,
18
+ extractFiles,
19
+ NetworkError,
20
+ PermissionError,
21
+ toBuffer,
22
+ ValidationError
23
+ } from "@chat-adapter/shared";
24
+ import {
25
+ convertEmojiPlaceholders,
15
26
  defaultEmojiResolver,
16
- isCardElement,
17
27
  NotImplementedError
18
28
  } from "chat";
19
29
 
20
30
  // src/cards.ts
21
31
  import {
22
- convertEmojiPlaceholders
23
- } from "chat";
24
- function convertEmoji(text) {
25
- return convertEmojiPlaceholders(text, "teams");
26
- }
32
+ createEmojiConverter,
33
+ mapButtonStyle,
34
+ cardToFallbackText as sharedCardToFallbackText
35
+ } from "@chat-adapter/shared";
36
+ var convertEmoji = createEmojiConverter("teams");
27
37
  var ADAPTIVE_CARD_SCHEMA = "http://adaptivecards.io/schemas/adaptive-card.json";
28
38
  var ADAPTIVE_CARD_VERSION = "1.4";
29
39
  function cardToAdaptiveCard(card) {
@@ -130,10 +140,9 @@ function convertButtonToAction(button) {
130
140
  value: button.value
131
141
  }
132
142
  };
133
- if (button.style === "primary") {
134
- action.style = "positive";
135
- } else if (button.style === "danger") {
136
- action.style = "destructive";
143
+ const style = mapButtonStyle(button.style, "teams");
144
+ if (style) {
145
+ action.style = style;
137
146
  }
138
147
  return action;
139
148
  }
@@ -165,39 +174,29 @@ function convertFieldsToElement(element) {
165
174
  };
166
175
  }
167
176
  function cardToFallbackText(card) {
168
- const parts = [];
169
- if (card.title) {
170
- parts.push(`**${convertEmoji(card.title)}**`);
171
- }
172
- if (card.subtitle) {
173
- parts.push(convertEmoji(card.subtitle));
174
- }
175
- for (const child of card.children) {
176
- const text = childToFallbackText(child);
177
- if (text) {
178
- parts.push(text);
179
- }
180
- }
181
- return parts.join("\n\n");
182
- }
183
- function childToFallbackText(child) {
184
- switch (child.type) {
185
- case "text":
186
- return convertEmoji(child.content);
187
- case "fields":
188
- return child.children.map((f) => `**${convertEmoji(f.label)}**: ${convertEmoji(f.value)}`).join("\n");
189
- case "actions":
190
- return `[${child.children.map((b) => convertEmoji(b.label)).join("] [")}]`;
191
- case "section":
192
- return child.children.map((c) => childToFallbackText(c)).filter(Boolean).join("\n");
193
- default:
194
- return null;
195
- }
177
+ return sharedCardToFallbackText(card, {
178
+ boldFormat: "**",
179
+ lineBreak: "\n\n",
180
+ platform: "teams"
181
+ });
196
182
  }
197
183
 
198
184
  // src/markdown.ts
199
185
  import {
200
186
  BaseFormatConverter,
187
+ getNodeChildren,
188
+ getNodeValue,
189
+ isBlockquoteNode,
190
+ isCodeNode,
191
+ isDeleteNode,
192
+ isEmphasisNode,
193
+ isInlineCodeNode,
194
+ isLinkNode,
195
+ isListItemNode,
196
+ isListNode,
197
+ isParagraphNode,
198
+ isStrongNode,
199
+ isTextNode,
201
200
  parseMarkdown
202
201
  } from "chat";
203
202
  var TeamsFormatConverter = class extends BaseFormatConverter {
@@ -231,11 +230,7 @@ var TeamsFormatConverter = class extends BaseFormatConverter {
231
230
  * Teams accepts standard markdown, so we just stringify cleanly.
232
231
  */
233
232
  fromAst(ast) {
234
- const parts = [];
235
- for (const node of ast.children) {
236
- parts.push(this.nodeToTeams(node));
237
- }
238
- return parts.join("\n\n");
233
+ return this.fromAstWithNodeConverter(ast, (node) => this.nodeToTeams(node));
239
234
  }
240
235
  /**
241
236
  * Parse Teams message into an AST.
@@ -264,55 +259,60 @@ var TeamsFormatConverter = class extends BaseFormatConverter {
264
259
  return parseMarkdown(markdown);
265
260
  }
266
261
  nodeToTeams(node) {
267
- switch (node.type) {
268
- case "paragraph":
269
- return node.children.map((child) => this.nodeToTeams(child)).join("");
270
- case "text": {
271
- const textValue = node.value;
272
- return textValue.replace(/@(\w+)/g, "<at>$1</at>");
273
- }
274
- case "strong":
275
- return `**${node.children.map((child) => this.nodeToTeams(child)).join("")}**`;
276
- case "emphasis":
277
- return `_${node.children.map((child) => this.nodeToTeams(child)).join("")}_`;
278
- case "delete":
279
- return `~~${node.children.map((child) => this.nodeToTeams(child)).join("")}~~`;
280
- case "inlineCode":
281
- return `\`${node.value}\``;
282
- case "code": {
283
- const codeNode = node;
284
- return `\`\`\`${codeNode.lang || ""}
285
- ${codeNode.value}
262
+ if (isParagraphNode(node)) {
263
+ return getNodeChildren(node).map((child) => this.nodeToTeams(child)).join("");
264
+ }
265
+ if (isTextNode(node)) {
266
+ return node.value.replace(/@(\w+)/g, "<at>$1</at>");
267
+ }
268
+ if (isStrongNode(node)) {
269
+ const content = getNodeChildren(node).map((child) => this.nodeToTeams(child)).join("");
270
+ return `**${content}**`;
271
+ }
272
+ if (isEmphasisNode(node)) {
273
+ const content = getNodeChildren(node).map((child) => this.nodeToTeams(child)).join("");
274
+ return `_${content}_`;
275
+ }
276
+ if (isDeleteNode(node)) {
277
+ const content = getNodeChildren(node).map((child) => this.nodeToTeams(child)).join("");
278
+ return `~~${content}~~`;
279
+ }
280
+ if (isInlineCodeNode(node)) {
281
+ return `\`${node.value}\``;
282
+ }
283
+ if (isCodeNode(node)) {
284
+ return `\`\`\`${node.lang || ""}
285
+ ${node.value}
286
286
  \`\`\``;
287
- }
288
- case "link": {
289
- const linkNode = node;
290
- const linkText = linkNode.children.map((child) => this.nodeToTeams(child)).join("");
291
- return `[${linkText}](${linkNode.url})`;
292
- }
293
- case "blockquote":
294
- return node.children.map((child) => `> ${this.nodeToTeams(child)}`).join("\n");
295
- case "list":
296
- return node.children.map((item, i) => {
297
- const prefix = node.ordered ? `${i + 1}.` : "-";
298
- const content = item.children.map((child) => this.nodeToTeams(child)).join("");
299
- return `${prefix} ${content}`;
300
- }).join("\n");
301
- case "listItem":
302
- return node.children.map((child) => this.nodeToTeams(child)).join("");
303
- case "break":
304
- return "\n";
305
- case "thematicBreak":
306
- return "---";
307
- default:
308
- if ("children" in node && Array.isArray(node.children)) {
309
- return node.children.map((child) => this.nodeToTeams(child)).join("");
310
- }
311
- if ("value" in node) {
312
- return String(node.value);
313
- }
314
- return "";
315
287
  }
288
+ if (isLinkNode(node)) {
289
+ const linkText = getNodeChildren(node).map((child) => this.nodeToTeams(child)).join("");
290
+ return `[${linkText}](${node.url})`;
291
+ }
292
+ if (isBlockquoteNode(node)) {
293
+ return getNodeChildren(node).map((child) => `> ${this.nodeToTeams(child)}`).join("\n");
294
+ }
295
+ if (isListNode(node)) {
296
+ return getNodeChildren(node).map((item, i) => {
297
+ const prefix = node.ordered ? `${i + 1}.` : "-";
298
+ const content = getNodeChildren(item).map((child) => this.nodeToTeams(child)).join("");
299
+ return `${prefix} ${content}`;
300
+ }).join("\n");
301
+ }
302
+ if (isListItemNode(node)) {
303
+ return getNodeChildren(node).map((child) => this.nodeToTeams(child)).join("");
304
+ }
305
+ if (node.type === "break") {
306
+ return "\n";
307
+ }
308
+ if (node.type === "thematicBreak") {
309
+ return "---";
310
+ }
311
+ const children = getNodeChildren(node);
312
+ if (children.length > 0) {
313
+ return children.map((child) => this.nodeToTeams(child)).join("");
314
+ }
315
+ return getNodeValue(node);
316
316
  }
317
317
  };
318
318
 
@@ -329,14 +329,18 @@ var TeamsAdapter = class {
329
329
  botAdapter;
330
330
  graphClient = null;
331
331
  chat = null;
332
- logger = null;
332
+ logger;
333
333
  formatConverter = new TeamsFormatConverter();
334
334
  config;
335
335
  constructor(config) {
336
336
  this.config = config;
337
+ this.logger = config.logger;
337
338
  this.userName = config.userName || "bot";
338
339
  if (config.appType === "SingleTenant" && !config.appTenantId) {
339
- throw new Error("appTenantId is required for SingleTenant app type");
340
+ throw new ValidationError(
341
+ "teams",
342
+ "appTenantId is required for SingleTenant app type"
343
+ );
340
344
  }
341
345
  const auth = new ConfigurationBotFrameworkAuthentication({
342
346
  MicrosoftAppId: config.appId,
@@ -362,16 +366,15 @@ var TeamsAdapter = class {
362
366
  }
363
367
  async initialize(chat) {
364
368
  this.chat = chat;
365
- this.logger = chat.getLogger(this.name);
366
369
  }
367
370
  async handleWebhook(request, options) {
368
371
  const body = await request.text();
369
- this.logger?.debug("Teams webhook raw body", { body });
372
+ this.logger.debug("Teams webhook raw body", { body });
370
373
  let activity;
371
374
  try {
372
375
  activity = JSON.parse(body);
373
376
  } catch (e) {
374
- this.logger?.error("Failed to parse request body", { error: e });
377
+ this.logger.error("Failed to parse request body", { error: e });
375
378
  return new Response("Invalid JSON", { status: 400 });
376
379
  }
377
380
  const authHeader = request.headers.get("authorization") || "";
@@ -388,7 +391,7 @@ var TeamsAdapter = class {
388
391
  headers: { "Content-Type": "application/json" }
389
392
  });
390
393
  } catch (error) {
391
- this.logger?.error("Bot adapter process error", { error });
394
+ this.logger.error("Bot adapter process error", { error });
392
395
  return new Response(JSON.stringify({ error: "Internal error" }), {
393
396
  status: 500,
394
397
  headers: { "Content-Type": "application/json" }
@@ -397,7 +400,7 @@ var TeamsAdapter = class {
397
400
  }
398
401
  async handleTurn(context, options) {
399
402
  if (!this.chat) {
400
- this.logger?.warn("Chat instance not initialized, ignoring event");
403
+ this.logger.warn("Chat instance not initialized, ignoring event");
401
404
  return;
402
405
  }
403
406
  const activity = context.activity;
@@ -406,9 +409,19 @@ var TeamsAdapter = class {
406
409
  const channelData = activity.channelData;
407
410
  const tenantId = channelData?.tenant?.id;
408
411
  const ttl = 30 * 24 * 60 * 60 * 1e3;
409
- this.chat.getState().set(`teams:serviceUrl:${userId}`, activity.serviceUrl, ttl);
412
+ this.chat.getState().set(`teams:serviceUrl:${userId}`, activity.serviceUrl, ttl).catch((err) => {
413
+ this.logger.error("Failed to cache serviceUrl", {
414
+ userId,
415
+ error: err
416
+ });
417
+ });
410
418
  if (tenantId) {
411
- this.chat.getState().set(`teams:tenantId:${userId}`, tenantId, ttl);
419
+ this.chat.getState().set(`teams:tenantId:${userId}`, tenantId, ttl).catch((err) => {
420
+ this.logger.error("Failed to cache tenantId", {
421
+ userId,
422
+ error: err
423
+ });
424
+ });
412
425
  }
413
426
  const team = channelData?.team;
414
427
  const teamAadGroupId = team?.aadGroupId;
@@ -423,11 +436,21 @@ var TeamsAdapter = class {
423
436
  tenantId
424
437
  };
425
438
  const contextJson = JSON.stringify(context2);
426
- this.chat.getState().set(`teams:channelContext:${baseChannelId}`, contextJson, ttl);
439
+ this.chat.getState().set(`teams:channelContext:${baseChannelId}`, contextJson, ttl).catch((err) => {
440
+ this.logger.error("Failed to cache channel context", {
441
+ conversationId: baseChannelId,
442
+ error: err
443
+ });
444
+ });
427
445
  if (teamThreadId) {
428
- this.chat.getState().set(`teams:teamContext:${teamThreadId}`, contextJson, ttl);
446
+ this.chat.getState().set(`teams:teamContext:${teamThreadId}`, contextJson, ttl).catch((err) => {
447
+ this.logger.error("Failed to cache team context", {
448
+ teamThreadId,
449
+ error: err
450
+ });
451
+ });
429
452
  }
430
- this.logger?.info(
453
+ this.logger.info(
431
454
  "Cached Teams team GUID from installation/update event",
432
455
  {
433
456
  activityType: activity.type,
@@ -444,8 +467,13 @@ var TeamsAdapter = class {
444
467
  `teams:channelContext:${baseChannelId}`,
445
468
  cachedTeamContext,
446
469
  ttl
447
- );
448
- this.logger?.info("Using cached Teams team GUID for channel", {
470
+ ).catch((err) => {
471
+ this.logger.error("Failed to cache channel context from team", {
472
+ conversationId: baseChannelId,
473
+ error: err
474
+ });
475
+ });
476
+ this.logger.info("Using cached Teams team GUID for channel", {
449
477
  conversationId: baseChannelId,
450
478
  teamThreadId
451
479
  });
@@ -459,9 +487,19 @@ var TeamsAdapter = class {
459
487
  tenantId
460
488
  };
461
489
  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(
490
+ this.chat.getState().set(`teams:channelContext:${baseChannelId}`, contextJson, ttl).catch((err) => {
491
+ this.logger.error("Failed to cache fetched channel context", {
492
+ conversationId: baseChannelId,
493
+ error: err
494
+ });
495
+ });
496
+ this.chat.getState().set(`teams:teamContext:${teamThreadId}`, contextJson, ttl).catch((err) => {
497
+ this.logger.error("Failed to cache fetched team context", {
498
+ teamThreadId,
499
+ error: err
500
+ });
501
+ });
502
+ this.logger.info(
465
503
  "Fetched and cached Teams team GUID via TeamsInfo API",
466
504
  {
467
505
  conversationId: baseChannelId,
@@ -472,7 +510,7 @@ var TeamsAdapter = class {
472
510
  );
473
511
  }
474
512
  } catch (error) {
475
- this.logger?.debug(
513
+ this.logger.debug(
476
514
  "Could not fetch team details (may not be a team scope)",
477
515
  { teamThreadId, error }
478
516
  );
@@ -489,7 +527,7 @@ var TeamsAdapter = class {
489
527
  return;
490
528
  }
491
529
  if (activity.type !== ActivityTypes.Message) {
492
- this.logger?.debug("Ignoring non-message activity", {
530
+ this.logger.debug("Ignoring non-message activity", {
493
531
  type: activity.type
494
532
  });
495
533
  return;
@@ -536,7 +574,7 @@ var TeamsAdapter = class {
536
574
  adapter: this,
537
575
  raw: activity
538
576
  };
539
- this.logger?.debug("Processing Teams message action (Action.Submit)", {
577
+ this.logger.debug("Processing Teams message action (Action.Submit)", {
540
578
  actionId: actionValue.actionId,
541
579
  value: actionValue.value,
542
580
  messageId: actionEvent.messageId,
@@ -553,7 +591,7 @@ var TeamsAdapter = class {
553
591
  await this.handleAdaptiveCardAction(context, activity, options);
554
592
  return;
555
593
  }
556
- this.logger?.debug("Ignoring unsupported invoke", {
594
+ this.logger.debug("Ignoring unsupported invoke", {
557
595
  name: activity.name
558
596
  });
559
597
  }
@@ -565,7 +603,7 @@ var TeamsAdapter = class {
565
603
  if (!this.chat) return;
566
604
  const actionData = activity.value?.action?.data;
567
605
  if (!actionData?.actionId) {
568
- this.logger?.debug("Adaptive card action missing actionId", {
606
+ this.logger.debug("Adaptive card action missing actionId", {
569
607
  value: activity.value
570
608
  });
571
609
  await context.sendActivity({
@@ -593,7 +631,7 @@ var TeamsAdapter = class {
593
631
  adapter: this,
594
632
  raw: activity
595
633
  };
596
- this.logger?.debug("Processing Teams adaptive card action", {
634
+ this.logger.debug("Processing Teams adaptive card action", {
597
635
  actionId: actionData.actionId,
598
636
  value: actionData.value,
599
637
  messageId: actionEvent.messageId,
@@ -637,7 +675,7 @@ var TeamsAdapter = class {
637
675
  threadId,
638
676
  raw: activity
639
677
  };
640
- this.logger?.debug("Processing Teams reaction added", {
678
+ this.logger.debug("Processing Teams reaction added", {
641
679
  emoji: emojiValue.name,
642
680
  rawEmoji,
643
681
  messageId
@@ -657,7 +695,7 @@ var TeamsAdapter = class {
657
695
  threadId,
658
696
  raw: activity
659
697
  };
660
- this.logger?.debug("Processing Teams reaction removed", {
698
+ this.logger.debug("Processing Teams reaction removed", {
661
699
  emoji: emojiValue.name,
662
700
  rawEmoji,
663
701
  messageId
@@ -718,7 +756,8 @@ var TeamsAdapter = class {
718
756
  fetchData: url ? async () => {
719
757
  const response = await fetch(url);
720
758
  if (!response.ok) {
721
- throw new Error(
759
+ throw new NetworkError(
760
+ "teams",
722
761
  `Failed to fetch file: ${response.status} ${response.statusText}`
723
762
  );
724
763
  }
@@ -732,9 +771,9 @@ var TeamsAdapter = class {
732
771
  }
733
772
  async postMessage(threadId, message) {
734
773
  const { conversationId, serviceUrl } = this.decodeThreadId(threadId);
735
- const files = this.extractFiles(message);
774
+ const files = extractFiles(message);
736
775
  const fileAttachments = files.length > 0 ? await this.filesToAttachments(files) : [];
737
- const card = this.extractCard(message);
776
+ const card = extractCard(message);
738
777
  let activity;
739
778
  if (card) {
740
779
  const adaptiveCard = cardToAdaptiveCard(card);
@@ -749,13 +788,13 @@ var TeamsAdapter = class {
749
788
  ...fileAttachments
750
789
  ]
751
790
  };
752
- this.logger?.debug("Teams API: sendActivity (adaptive card)", {
791
+ this.logger.debug("Teams API: sendActivity (adaptive card)", {
753
792
  conversationId,
754
793
  serviceUrl,
755
794
  fileCount: fileAttachments.length
756
795
  });
757
796
  } else {
758
- const text = convertEmojiPlaceholders2(
797
+ const text = convertEmojiPlaceholders(
759
798
  this.formatConverter.renderPostable(message),
760
799
  "teams"
761
800
  );
@@ -765,7 +804,7 @@ var TeamsAdapter = class {
765
804
  textFormat: "markdown",
766
805
  attachments: fileAttachments.length > 0 ? fileAttachments : void 0
767
806
  };
768
- this.logger?.debug("Teams API: sendActivity (message)", {
807
+ this.logger.debug("Teams API: sendActivity (message)", {
769
808
  conversationId,
770
809
  serviceUrl,
771
810
  textLength: text.length,
@@ -778,42 +817,29 @@ var TeamsAdapter = class {
778
817
  conversation: { id: conversationId }
779
818
  };
780
819
  let messageId = "";
781
- await this.botAdapter.continueConversationAsync(
782
- this.config.appId,
783
- conversationReference,
784
- async (context) => {
785
- const response = await context.sendActivity(activity);
786
- messageId = response?.id || "";
787
- }
788
- );
789
- this.logger?.debug("Teams API: sendActivity response", { messageId });
820
+ try {
821
+ await this.botAdapter.continueConversationAsync(
822
+ this.config.appId,
823
+ conversationReference,
824
+ async (context) => {
825
+ const response = await context.sendActivity(activity);
826
+ messageId = response?.id || "";
827
+ }
828
+ );
829
+ } catch (error) {
830
+ this.logger.error("Teams API: sendActivity failed", {
831
+ conversationId,
832
+ error
833
+ });
834
+ this.handleTeamsError(error, "postMessage");
835
+ }
836
+ this.logger.debug("Teams API: sendActivity response", { messageId });
790
837
  return {
791
838
  id: messageId,
792
839
  threadId,
793
840
  raw: activity
794
841
  };
795
842
  }
796
- /**
797
- * Extract card element from a AdapterPostableMessage if present.
798
- */
799
- extractCard(message) {
800
- if (isCardElement(message)) {
801
- return message;
802
- }
803
- if (typeof message === "object" && message !== null && "card" in message) {
804
- return message.card;
805
- }
806
- return null;
807
- }
808
- /**
809
- * Extract files from a AdapterPostableMessage if present.
810
- */
811
- extractFiles(message) {
812
- if (typeof message === "object" && message !== null && "files" in message) {
813
- return message.files ?? [];
814
- }
815
- return [];
816
- }
817
843
  /**
818
844
  * Convert files to Teams attachments.
819
845
  * Uses inline data URIs for small files.
@@ -821,20 +847,15 @@ var TeamsAdapter = class {
821
847
  async filesToAttachments(files) {
822
848
  const attachments = [];
823
849
  for (const file of files) {
824
- let buffer;
825
- if (Buffer.isBuffer(file.data)) {
826
- buffer = file.data;
827
- } else if (file.data instanceof ArrayBuffer) {
828
- buffer = Buffer.from(file.data);
829
- } else if (file.data instanceof Blob) {
830
- const arrayBuffer = await file.data.arrayBuffer();
831
- buffer = Buffer.from(arrayBuffer);
832
- } else {
850
+ const buffer = await toBuffer(file.data, {
851
+ platform: "teams",
852
+ throwOnUnsupported: false
853
+ });
854
+ if (!buffer) {
833
855
  continue;
834
856
  }
835
857
  const mimeType = file.mimeType || "application/octet-stream";
836
- const base64 = buffer.toString("base64");
837
- const dataUri = `data:${mimeType};base64,${base64}`;
858
+ const dataUri = bufferToDataUri(buffer, mimeType);
838
859
  attachments.push({
839
860
  contentType: mimeType,
840
861
  contentUrl: dataUri,
@@ -845,7 +866,7 @@ var TeamsAdapter = class {
845
866
  }
846
867
  async editMessage(threadId, messageId, message) {
847
868
  const { conversationId, serviceUrl } = this.decodeThreadId(threadId);
848
- const card = this.extractCard(message);
869
+ const card = extractCard(message);
849
870
  let activity;
850
871
  if (card) {
851
872
  const adaptiveCard = cardToAdaptiveCard(card);
@@ -860,12 +881,12 @@ var TeamsAdapter = class {
860
881
  }
861
882
  ]
862
883
  };
863
- this.logger?.debug("Teams API: updateActivity (adaptive card)", {
884
+ this.logger.debug("Teams API: updateActivity (adaptive card)", {
864
885
  conversationId,
865
886
  messageId
866
887
  });
867
888
  } else {
868
- const text = convertEmojiPlaceholders2(
889
+ const text = convertEmojiPlaceholders(
869
890
  this.formatConverter.renderPostable(message),
870
891
  "teams"
871
892
  );
@@ -875,7 +896,7 @@ var TeamsAdapter = class {
875
896
  text,
876
897
  textFormat: "markdown"
877
898
  };
878
- this.logger?.debug("Teams API: updateActivity", {
899
+ this.logger.debug("Teams API: updateActivity", {
879
900
  conversationId,
880
901
  messageId,
881
902
  textLength: text.length
@@ -886,14 +907,23 @@ var TeamsAdapter = class {
886
907
  serviceUrl,
887
908
  conversation: { id: conversationId }
888
909
  };
889
- await this.botAdapter.continueConversationAsync(
890
- this.config.appId,
891
- conversationReference,
892
- async (context) => {
893
- await context.updateActivity(activity);
894
- }
895
- );
896
- this.logger?.debug("Teams API: updateActivity response", { ok: true });
910
+ try {
911
+ await this.botAdapter.continueConversationAsync(
912
+ this.config.appId,
913
+ conversationReference,
914
+ async (context) => {
915
+ await context.updateActivity(activity);
916
+ }
917
+ );
918
+ } catch (error) {
919
+ this.logger.error("Teams API: updateActivity failed", {
920
+ conversationId,
921
+ messageId,
922
+ error
923
+ });
924
+ this.handleTeamsError(error, "editMessage");
925
+ }
926
+ this.logger.debug("Teams API: updateActivity response", { ok: true });
897
927
  return {
898
928
  id: messageId,
899
929
  threadId,
@@ -907,18 +937,27 @@ var TeamsAdapter = class {
907
937
  serviceUrl,
908
938
  conversation: { id: conversationId }
909
939
  };
910
- this.logger?.debug("Teams API: deleteActivity", {
940
+ this.logger.debug("Teams API: deleteActivity", {
911
941
  conversationId,
912
942
  messageId
913
943
  });
914
- await this.botAdapter.continueConversationAsync(
915
- this.config.appId,
916
- conversationReference,
917
- async (context) => {
918
- await context.deleteActivity(messageId);
919
- }
920
- );
921
- this.logger?.debug("Teams API: deleteActivity response", { ok: true });
944
+ try {
945
+ await this.botAdapter.continueConversationAsync(
946
+ this.config.appId,
947
+ conversationReference,
948
+ async (context) => {
949
+ await context.deleteActivity(messageId);
950
+ }
951
+ );
952
+ } catch (error) {
953
+ this.logger.error("Teams API: deleteActivity failed", {
954
+ conversationId,
955
+ messageId,
956
+ error
957
+ });
958
+ this.handleTeamsError(error, "deleteMessage");
959
+ }
960
+ this.logger.debug("Teams API: deleteActivity response", { ok: true });
922
961
  }
923
962
  async addReaction(_threadId, _messageId, _emoji) {
924
963
  throw new NotImplementedError(
@@ -939,15 +978,23 @@ var TeamsAdapter = class {
939
978
  serviceUrl,
940
979
  conversation: { id: conversationId }
941
980
  };
942
- this.logger?.debug("Teams API: sendActivity (typing)", { conversationId });
943
- await this.botAdapter.continueConversationAsync(
944
- this.config.appId,
945
- conversationReference,
946
- async (context) => {
947
- await context.sendActivity({ type: ActivityTypes.Typing });
948
- }
949
- );
950
- this.logger?.debug("Teams API: sendActivity (typing) response", {
981
+ this.logger.debug("Teams API: sendActivity (typing)", { conversationId });
982
+ try {
983
+ await this.botAdapter.continueConversationAsync(
984
+ this.config.appId,
985
+ conversationReference,
986
+ async (context) => {
987
+ await context.sendActivity({ type: ActivityTypes.Typing });
988
+ }
989
+ );
990
+ } catch (error) {
991
+ this.logger.error("Teams API: sendActivity (typing) failed", {
992
+ conversationId,
993
+ error
994
+ });
995
+ this.handleTeamsError(error, "startTyping");
996
+ }
997
+ this.logger.debug("Teams API: sendActivity (typing) response", {
951
998
  ok: true
952
999
  });
953
1000
  }
@@ -963,7 +1010,7 @@ var TeamsAdapter = class {
963
1010
  const cachedTenantId = await this.chat?.getState().get(`teams:tenantId:${userId}`);
964
1011
  const serviceUrl = cachedServiceUrl || "https://smba.trafficmanager.net/teams/";
965
1012
  const tenantId = cachedTenantId || this.config.appTenantId;
966
- this.logger?.debug("Teams: creating 1:1 conversation", {
1013
+ this.logger.debug("Teams: creating 1:1 conversation", {
967
1014
  userId,
968
1015
  serviceUrl,
969
1016
  tenantId,
@@ -971,7 +1018,8 @@ var TeamsAdapter = class {
971
1018
  cachedTenantId: !!cachedTenantId
972
1019
  });
973
1020
  if (!tenantId) {
974
- throw new Error(
1021
+ throw new ValidationError(
1022
+ "teams",
975
1023
  "Cannot open DM: tenant ID not found. User must interact with the bot first (via @mention) to cache their tenant ID."
976
1024
  );
977
1025
  }
@@ -993,16 +1041,19 @@ var TeamsAdapter = class {
993
1041
  },
994
1042
  async (turnContext) => {
995
1043
  conversationId = turnContext?.activity?.conversation?.id || "";
996
- this.logger?.debug("Teams: conversation created in callback", {
1044
+ this.logger.debug("Teams: conversation created in callback", {
997
1045
  conversationId,
998
1046
  activityId: turnContext?.activity?.id
999
1047
  });
1000
1048
  }
1001
1049
  );
1002
1050
  if (!conversationId) {
1003
- throw new Error("Failed to create 1:1 conversation - no ID returned");
1051
+ throw new NetworkError(
1052
+ "teams",
1053
+ "Failed to create 1:1 conversation - no ID returned"
1054
+ );
1004
1055
  }
1005
- this.logger?.debug("Teams: 1:1 conversation created", { conversationId });
1056
+ this.logger.debug("Teams: 1:1 conversation created", { conversationId });
1006
1057
  return this.encodeThreadId({
1007
1058
  conversationId,
1008
1059
  serviceUrl
@@ -1033,7 +1084,7 @@ var TeamsAdapter = class {
1033
1084
  }
1034
1085
  }
1035
1086
  try {
1036
- this.logger?.debug("Teams Graph API: fetching messages", {
1087
+ this.logger.debug("Teams Graph API: fetching messages", {
1037
1088
  conversationId: baseConversationId,
1038
1089
  threadMessageId,
1039
1090
  hasChannelContext: !!channelContext,
@@ -1086,12 +1137,12 @@ var TeamsAdapter = class {
1086
1137
  graphMessages = graphMessages.filter((msg) => {
1087
1138
  return msg.id && msg.id >= threadMessageId;
1088
1139
  });
1089
- this.logger?.debug("Filtered group chat messages to thread", {
1140
+ this.logger.debug("Filtered group chat messages to thread", {
1090
1141
  threadMessageId,
1091
1142
  filteredCount: graphMessages.length
1092
1143
  });
1093
1144
  }
1094
- this.logger?.debug("Teams Graph API: fetched messages", {
1145
+ this.logger.debug("Teams Graph API: fetched messages", {
1095
1146
  count: graphMessages.length,
1096
1147
  direction,
1097
1148
  hasMoreMessages
@@ -1136,7 +1187,7 @@ var TeamsAdapter = class {
1136
1187
  }
1137
1188
  return { messages, nextCursor };
1138
1189
  } catch (error) {
1139
- this.logger?.error("Teams Graph API: fetchMessages error", { error });
1190
+ this.logger.error("Teams Graph API: fetchMessages error", { error });
1140
1191
  if (error instanceof Error && error.message?.includes("403")) {
1141
1192
  throw new NotImplementedError(
1142
1193
  "Teams fetchMessages requires one of these Azure AD app permissions: ChatMessage.Read.Chat, Chat.Read.All, or Chat.Read.WhereInstalled",
@@ -1156,7 +1207,7 @@ var TeamsAdapter = class {
1156
1207
  const limit = options.limit || 50;
1157
1208
  const cursor = options.cursor;
1158
1209
  const direction = options.direction ?? "backward";
1159
- this.logger?.debug("Teams Graph API: fetching channel thread messages", {
1210
+ this.logger.debug("Teams Graph API: fetching channel thread messages", {
1160
1211
  teamId: context.teamId,
1161
1212
  channelId: context.channelId,
1162
1213
  threadMessageId,
@@ -1168,13 +1219,13 @@ var TeamsAdapter = class {
1168
1219
  const repliesUrl = `${parentUrl}/replies`;
1169
1220
  const graphClient = this.graphClient;
1170
1221
  if (!graphClient) {
1171
- throw new Error("Graph client not initialized");
1222
+ throw new AuthenticationError("teams", "Graph client not initialized");
1172
1223
  }
1173
1224
  let parentMessage = null;
1174
1225
  try {
1175
1226
  parentMessage = await graphClient.api(parentUrl).get();
1176
1227
  } catch (err) {
1177
- this.logger?.warn("Failed to fetch parent message", {
1228
+ this.logger.warn("Failed to fetch parent message", {
1178
1229
  threadMessageId,
1179
1230
  err
1180
1231
  });
@@ -1231,7 +1282,7 @@ var TeamsAdapter = class {
1231
1282
  hasMoreMessages = allMessages.length > limit;
1232
1283
  }
1233
1284
  }
1234
- this.logger?.debug("Teams Graph API: fetched channel thread messages", {
1285
+ this.logger.debug("Teams Graph API: fetched channel thread messages", {
1235
1286
  count: graphMessages.length,
1236
1287
  direction,
1237
1288
  hasMoreMessages
@@ -1376,7 +1427,10 @@ var TeamsAdapter = class {
1376
1427
  decodeThreadId(threadId) {
1377
1428
  const parts = threadId.split(":");
1378
1429
  if (parts.length !== 3 || parts[0] !== "teams") {
1379
- throw new Error(`Invalid Teams thread ID: ${threadId}`);
1430
+ throw new ValidationError(
1431
+ "teams",
1432
+ `Invalid Teams thread ID: ${threadId}`
1433
+ );
1380
1434
  }
1381
1435
  const conversationId = Buffer.from(
1382
1436
  parts[1],
@@ -1421,6 +1475,47 @@ var TeamsAdapter = class {
1421
1475
  renderFormatted(content) {
1422
1476
  return this.formatConverter.fromAst(content);
1423
1477
  }
1478
+ /**
1479
+ * Convert Teams/BotBuilder errors to standardized AdapterError types.
1480
+ */
1481
+ handleTeamsError(error, operation) {
1482
+ if (error && typeof error === "object") {
1483
+ const err = error;
1484
+ const statusCode = err.statusCode || err.status || err.code;
1485
+ if (statusCode === 401 || statusCode === 403) {
1486
+ throw new AuthenticationError(
1487
+ "teams",
1488
+ `Authentication failed for ${operation}: ${err.message || "unauthorized"}`
1489
+ );
1490
+ }
1491
+ if (statusCode === 404) {
1492
+ throw new NetworkError(
1493
+ "teams",
1494
+ `Resource not found during ${operation}: conversation or message may no longer exist`,
1495
+ error instanceof Error ? error : void 0
1496
+ );
1497
+ }
1498
+ if (statusCode === 429) {
1499
+ const retryAfter = typeof err.retryAfter === "number" ? err.retryAfter : void 0;
1500
+ throw new AdapterRateLimitError("teams", retryAfter);
1501
+ }
1502
+ if (statusCode === 403 || err.message && typeof err.message === "string" && err.message.toLowerCase().includes("permission")) {
1503
+ throw new PermissionError("teams", operation);
1504
+ }
1505
+ if (err.message && typeof err.message === "string") {
1506
+ throw new NetworkError(
1507
+ "teams",
1508
+ `Teams API error during ${operation}: ${err.message}`,
1509
+ error instanceof Error ? error : void 0
1510
+ );
1511
+ }
1512
+ }
1513
+ throw new NetworkError(
1514
+ "teams",
1515
+ `Teams API error during ${operation}: ${String(error)}`,
1516
+ error instanceof Error ? error : void 0
1517
+ );
1518
+ }
1424
1519
  };
1425
1520
  function createTeamsAdapter(config) {
1426
1521
  return new TeamsAdapter(config);