@kodelyth/msteams 2026.5.42 → 2026.6.2

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.
Files changed (177) hide show
  1. package/klaw.plugin.json +726 -2
  2. package/package.json +18 -6
  3. package/api.ts +0 -3
  4. package/channel-config-api.ts +0 -1
  5. package/channel-plugin-api.ts +0 -2
  6. package/config-api.ts +0 -4
  7. package/contract-api.ts +0 -4
  8. package/index.ts +0 -20
  9. package/runtime-api.ts +0 -66
  10. package/secret-contract-api.ts +0 -5
  11. package/setup-entry.ts +0 -13
  12. package/setup-plugin-api.ts +0 -3
  13. package/src/ai-entity.ts +0 -7
  14. package/src/approval-auth.ts +0 -44
  15. package/src/attachments/bot-framework.test.ts +0 -506
  16. package/src/attachments/bot-framework.ts +0 -348
  17. package/src/attachments/download.ts +0 -328
  18. package/src/attachments/graph.test.ts +0 -441
  19. package/src/attachments/graph.ts +0 -489
  20. package/src/attachments/html.ts +0 -122
  21. package/src/attachments/payload.ts +0 -14
  22. package/src/attachments/remote-media.test.ts +0 -187
  23. package/src/attachments/remote-media.ts +0 -86
  24. package/src/attachments/shared.test.ts +0 -547
  25. package/src/attachments/shared.ts +0 -655
  26. package/src/attachments/types.ts +0 -47
  27. package/src/attachments.graph.test.ts +0 -414
  28. package/src/attachments.helpers.test.ts +0 -245
  29. package/src/attachments.test-helpers.ts +0 -17
  30. package/src/attachments.test.ts +0 -754
  31. package/src/attachments.ts +0 -18
  32. package/src/block-streaming-config.test.ts +0 -61
  33. package/src/channel-api.ts +0 -1
  34. package/src/channel.actions.test.ts +0 -797
  35. package/src/channel.directory.test.ts +0 -176
  36. package/src/channel.message-adapter.test.ts +0 -227
  37. package/src/channel.runtime.ts +0 -56
  38. package/src/channel.setup.ts +0 -77
  39. package/src/channel.test.ts +0 -136
  40. package/src/channel.ts +0 -1176
  41. package/src/config-schema.ts +0 -6
  42. package/src/config-ui-hints.ts +0 -40
  43. package/src/conversation-store-fs.test.ts +0 -81
  44. package/src/conversation-store-fs.ts +0 -149
  45. package/src/conversation-store-helpers.test.ts +0 -202
  46. package/src/conversation-store-helpers.ts +0 -105
  47. package/src/conversation-store-memory.ts +0 -51
  48. package/src/conversation-store.shared.test.ts +0 -260
  49. package/src/conversation-store.ts +0 -71
  50. package/src/directory-live.test.ts +0 -156
  51. package/src/directory-live.ts +0 -111
  52. package/src/doctor.ts +0 -27
  53. package/src/errors.test.ts +0 -154
  54. package/src/errors.ts +0 -270
  55. package/src/feedback-reflection-prompt.ts +0 -117
  56. package/src/feedback-reflection-store.ts +0 -113
  57. package/src/feedback-reflection.test.ts +0 -237
  58. package/src/feedback-reflection.ts +0 -268
  59. package/src/file-consent-helpers.test.ts +0 -328
  60. package/src/file-consent-helpers.ts +0 -115
  61. package/src/file-consent-invoke.ts +0 -150
  62. package/src/file-consent.test.ts +0 -378
  63. package/src/file-consent.ts +0 -223
  64. package/src/graph-chat.ts +0 -36
  65. package/src/graph-group-management.test.ts +0 -332
  66. package/src/graph-group-management.ts +0 -168
  67. package/src/graph-members.test.ts +0 -89
  68. package/src/graph-members.ts +0 -48
  69. package/src/graph-messages.actions.test.ts +0 -253
  70. package/src/graph-messages.read.test.ts +0 -391
  71. package/src/graph-messages.search.test.ts +0 -227
  72. package/src/graph-messages.test-helpers.ts +0 -50
  73. package/src/graph-messages.ts +0 -534
  74. package/src/graph-teams.test.ts +0 -222
  75. package/src/graph-teams.ts +0 -114
  76. package/src/graph-thread.test.ts +0 -252
  77. package/src/graph-thread.ts +0 -146
  78. package/src/graph-upload.test.ts +0 -253
  79. package/src/graph-upload.ts +0 -531
  80. package/src/graph-users.ts +0 -29
  81. package/src/graph.test.ts +0 -540
  82. package/src/graph.ts +0 -308
  83. package/src/inbound.test.ts +0 -221
  84. package/src/inbound.ts +0 -148
  85. package/src/index.ts +0 -4
  86. package/src/media-helpers.test.ts +0 -220
  87. package/src/media-helpers.ts +0 -105
  88. package/src/mentions.test.ts +0 -254
  89. package/src/mentions.ts +0 -114
  90. package/src/messenger.test.ts +0 -961
  91. package/src/messenger.ts +0 -608
  92. package/src/monitor-handler/access.ts +0 -136
  93. package/src/monitor-handler/inbound-media.test.ts +0 -314
  94. package/src/monitor-handler/inbound-media.ts +0 -180
  95. package/src/monitor-handler/message-handler-mock-support.test-support.ts +0 -28
  96. package/src/monitor-handler/message-handler.authz.test.ts +0 -739
  97. package/src/monitor-handler/message-handler.dm-media.test.ts +0 -54
  98. package/src/monitor-handler/message-handler.test-support.ts +0 -99
  99. package/src/monitor-handler/message-handler.thread-parent.test.ts +0 -225
  100. package/src/monitor-handler/message-handler.thread-session.test.ts +0 -132
  101. package/src/monitor-handler/message-handler.ts +0 -1003
  102. package/src/monitor-handler/reaction-handler.test.ts +0 -325
  103. package/src/monitor-handler/reaction-handler.ts +0 -122
  104. package/src/monitor-handler/thread-session.ts +0 -30
  105. package/src/monitor-handler.adaptive-card.test.ts +0 -158
  106. package/src/monitor-handler.feedback-authz.test.ts +0 -357
  107. package/src/monitor-handler.file-consent.test.ts +0 -443
  108. package/src/monitor-handler.sso.test.ts +0 -576
  109. package/src/monitor-handler.test-helpers.ts +0 -181
  110. package/src/monitor-handler.ts +0 -538
  111. package/src/monitor-handler.types.ts +0 -27
  112. package/src/monitor-types.ts +0 -6
  113. package/src/monitor.lifecycle.test.ts +0 -457
  114. package/src/monitor.test.ts +0 -119
  115. package/src/monitor.ts +0 -476
  116. package/src/oauth.flow.ts +0 -77
  117. package/src/oauth.shared.ts +0 -37
  118. package/src/oauth.test.ts +0 -350
  119. package/src/oauth.token.ts +0 -162
  120. package/src/oauth.ts +0 -130
  121. package/src/outbound.test.ts +0 -400
  122. package/src/outbound.ts +0 -198
  123. package/src/pending-uploads-fs.test.ts +0 -261
  124. package/src/pending-uploads-fs.ts +0 -235
  125. package/src/pending-uploads.test.ts +0 -186
  126. package/src/pending-uploads.ts +0 -121
  127. package/src/policy.test.ts +0 -156
  128. package/src/policy.ts +0 -245
  129. package/src/polls-store-memory.ts +0 -32
  130. package/src/polls.test.ts +0 -169
  131. package/src/polls.ts +0 -312
  132. package/src/presentation.ts +0 -93
  133. package/src/probe.test.ts +0 -79
  134. package/src/probe.ts +0 -132
  135. package/src/reply-dispatcher.test.ts +0 -543
  136. package/src/reply-dispatcher.ts +0 -523
  137. package/src/reply-stream-controller.test.ts +0 -424
  138. package/src/reply-stream-controller.ts +0 -334
  139. package/src/resolve-allowlist.test.ts +0 -253
  140. package/src/resolve-allowlist.ts +0 -309
  141. package/src/revoked-context.ts +0 -17
  142. package/src/runtime.ts +0 -12
  143. package/src/sdk-types.ts +0 -59
  144. package/src/sdk.test.ts +0 -727
  145. package/src/sdk.ts +0 -916
  146. package/src/secret-contract.ts +0 -49
  147. package/src/secret-input.ts +0 -7
  148. package/src/send-context.test.ts +0 -93
  149. package/src/send-context.ts +0 -269
  150. package/src/send.test.ts +0 -588
  151. package/src/send.ts +0 -697
  152. package/src/sent-message-cache.test.ts +0 -106
  153. package/src/sent-message-cache.ts +0 -174
  154. package/src/session-route.ts +0 -40
  155. package/src/setup-core.ts +0 -162
  156. package/src/setup-surface.test.ts +0 -175
  157. package/src/setup-surface.ts +0 -319
  158. package/src/sso-token-store.test.ts +0 -74
  159. package/src/sso-token-store.ts +0 -166
  160. package/src/sso.ts +0 -300
  161. package/src/storage.ts +0 -25
  162. package/src/store-fs.ts +0 -42
  163. package/src/streaming-message.test.ts +0 -323
  164. package/src/streaming-message.ts +0 -327
  165. package/src/test-runtime.ts +0 -16
  166. package/src/thread-parent-context.test.ts +0 -224
  167. package/src/thread-parent-context.ts +0 -159
  168. package/src/token-response.ts +0 -11
  169. package/src/token.test.ts +0 -268
  170. package/src/token.ts +0 -194
  171. package/src/user-agent.test.ts +0 -121
  172. package/src/user-agent.ts +0 -53
  173. package/src/webhook-timeouts.ts +0 -27
  174. package/src/welcome-card.test.ts +0 -104
  175. package/src/welcome-card.ts +0 -57
  176. package/test-api.ts +0 -1
  177. package/tsconfig.json +0 -16
package/src/send.ts DELETED
@@ -1,697 +0,0 @@
1
- import {
2
- createMessageReceiptFromOutboundResults,
3
- type MessageReceipt,
4
- type MessageReceiptPartKind,
5
- } from "klaw/plugin-sdk/channel-message";
6
- import { resolveMarkdownTableMode } from "klaw/plugin-sdk/markdown-table-runtime";
7
- import { convertMarkdownTables } from "klaw/plugin-sdk/text-chunking";
8
- import { loadOutboundMediaFromUrl, type KlawConfig } from "../runtime-api.js";
9
- import {
10
- classifyMSTeamsSendError,
11
- formatMSTeamsSendErrorHint,
12
- formatUnknownError,
13
- } from "./errors.js";
14
- import { prepareFileConsentActivityFs, requiresFileConsent } from "./file-consent-helpers.js";
15
- import { buildTeamsFileInfoCard } from "./graph-chat.js";
16
- import {
17
- getDriveItemProperties,
18
- uploadAndShareOneDrive,
19
- uploadAndShareSharePoint,
20
- } from "./graph-upload.js";
21
- import { extractFilename, extractMessageId } from "./media-helpers.js";
22
- import { buildConversationReference, sendMSTeamsMessages } from "./messenger.js";
23
- import { setPendingUploadActivityIdFs } from "./pending-uploads-fs.js";
24
- import { setPendingUploadActivityId } from "./pending-uploads.js";
25
- import { buildMSTeamsPollCard } from "./polls.js";
26
- import { resolveMSTeamsSendContext, type MSTeamsProactiveContext } from "./send-context.js";
27
-
28
- type SendMSTeamsMessageParams = {
29
- /** Full config (for credentials) */
30
- cfg: KlawConfig;
31
- /** Conversation ID or user ID to send to */
32
- to: string;
33
- /** Message text */
34
- text: string;
35
- /** Optional media URL */
36
- mediaUrl?: string;
37
- /** Optional filename override for uploaded media/files */
38
- filename?: string;
39
- mediaLocalRoots?: readonly string[];
40
- mediaReadFile?: (filePath: string) => Promise<Buffer>;
41
- };
42
-
43
- type SendMSTeamsMessageResult = {
44
- messageId: string;
45
- conversationId: string;
46
- receipt: MessageReceipt;
47
- /** If a FileConsentCard was sent instead of the file, this contains the upload ID */
48
- pendingUploadId?: string;
49
- };
50
-
51
- /** Threshold for large files that require FileConsentCard flow in personal chats */
52
- const FILE_CONSENT_THRESHOLD_BYTES = 4 * 1024 * 1024; // 4MB
53
-
54
- /**
55
- * MSTeams-specific media size limit (100MB).
56
- * Higher than the default because OneDrive upload handles large files well.
57
- */
58
- const MSTEAMS_MAX_MEDIA_BYTES = 100 * 1024 * 1024;
59
-
60
- function createMSTeamsSendReceipt(params: {
61
- conversationId: string;
62
- platformMessageIds: readonly string[];
63
- kind: MessageReceiptPartKind;
64
- }) {
65
- return createMessageReceiptFromOutboundResults({
66
- kind: params.kind,
67
- results: params.platformMessageIds.map((messageId) => ({
68
- channel: "msteams",
69
- messageId,
70
- conversationId: params.conversationId,
71
- })),
72
- });
73
- }
74
-
75
- function createMSTeamsSendResult(params: {
76
- conversationId: string;
77
- messageId: string;
78
- platformMessageIds?: readonly string[];
79
- kind: MessageReceiptPartKind;
80
- pendingUploadId?: string;
81
- }): SendMSTeamsMessageResult {
82
- const platformMessageIds = (
83
- params.platformMessageIds?.length ? [...params.platformMessageIds] : [params.messageId]
84
- )
85
- .map((messageId) => messageId.trim())
86
- .filter((messageId) => messageId && messageId !== "unknown");
87
- return {
88
- messageId: params.messageId,
89
- conversationId: params.conversationId,
90
- receipt: createMSTeamsSendReceipt({
91
- conversationId: params.conversationId,
92
- platformMessageIds,
93
- kind: params.kind,
94
- }),
95
- ...(params.pendingUploadId ? { pendingUploadId: params.pendingUploadId } : {}),
96
- };
97
- }
98
-
99
- type SendMSTeamsPollParams = {
100
- /** Full config (for credentials) */
101
- cfg: KlawConfig;
102
- /** Conversation ID or user ID to send to */
103
- to: string;
104
- /** Poll question */
105
- question: string;
106
- /** Poll options */
107
- options: string[];
108
- /** Max selections (defaults to 1) */
109
- maxSelections?: number;
110
- };
111
-
112
- type SendMSTeamsPollResult = {
113
- pollId: string;
114
- messageId: string;
115
- conversationId: string;
116
- };
117
-
118
- type SendMSTeamsCardParams = {
119
- /** Full config (for credentials) */
120
- cfg: KlawConfig;
121
- /** Conversation ID or user ID to send to */
122
- to: string;
123
- /** Adaptive Card JSON object */
124
- card: Record<string, unknown>;
125
- };
126
-
127
- type SendMSTeamsCardResult = {
128
- messageId: string;
129
- conversationId: string;
130
- };
131
-
132
- /**
133
- * Send a message to a Teams conversation or user.
134
- *
135
- * Uses the stored ConversationReference from previous interactions.
136
- * The bot must have received at least one message from the conversation
137
- * before proactive messaging works.
138
- *
139
- * File handling by conversation type:
140
- * - Personal (1:1) chats: small images (<4MB) use base64, large files and non-images use FileConsentCard
141
- * - Group chats / channels: files are uploaded to OneDrive and shared via link
142
- */
143
- export async function sendMessageMSTeams(
144
- params: SendMSTeamsMessageParams,
145
- ): Promise<SendMSTeamsMessageResult> {
146
- const { cfg, to, text, mediaUrl, filename, mediaLocalRoots, mediaReadFile } = params;
147
- const tableMode = resolveMarkdownTableMode({
148
- cfg,
149
- channel: "msteams",
150
- });
151
- const messageText = convertMarkdownTables(text ?? "", tableMode);
152
- const ctx = await resolveMSTeamsSendContext({ cfg, to });
153
- const {
154
- adapter,
155
- appId,
156
- conversationId,
157
- ref,
158
- log,
159
- conversationType,
160
- tokenProvider,
161
- sharePointSiteId,
162
- } = ctx;
163
-
164
- log.debug?.("sending proactive message", {
165
- conversationId,
166
- conversationType,
167
- textLength: messageText.length,
168
- hasMedia: Boolean(mediaUrl),
169
- });
170
-
171
- // Handle media if present
172
- if (mediaUrl) {
173
- const mediaMaxBytes = ctx.mediaMaxBytes ?? MSTEAMS_MAX_MEDIA_BYTES;
174
- const media = await loadOutboundMediaFromUrl(mediaUrl, {
175
- maxBytes: mediaMaxBytes,
176
- mediaLocalRoots,
177
- mediaReadFile,
178
- });
179
- const isLargeFile = media.buffer.length >= FILE_CONSENT_THRESHOLD_BYTES;
180
- const isImage = media.contentType?.startsWith("image/") ?? false;
181
- const fallbackFileName = await extractFilename(mediaUrl);
182
- const fileName = filename?.trim() || media.fileName || fallbackFileName;
183
-
184
- log.debug?.("processing media", {
185
- fileName,
186
- contentType: media.contentType,
187
- size: media.buffer.length,
188
- isLargeFile,
189
- isImage,
190
- conversationType,
191
- });
192
-
193
- // Personal chats: base64 only works for images; use FileConsentCard for large files or non-images
194
- if (
195
- requiresFileConsent({
196
- conversationType,
197
- contentType: media.contentType,
198
- bufferSize: media.buffer.length,
199
- thresholdBytes: FILE_CONSENT_THRESHOLD_BYTES,
200
- })
201
- ) {
202
- // Proactive CLI sends run in a different process from the gateway's
203
- // monitor that receives the fileConsent/invoke callback. Use the FS-
204
- // backed helper so the invoke handler can find the pending upload when
205
- // the user clicks "Allow".
206
- const { activity, uploadId } = await prepareFileConsentActivityFs({
207
- media: { buffer: media.buffer, filename: fileName, contentType: media.contentType },
208
- conversationId,
209
- description: messageText || undefined,
210
- });
211
-
212
- log.debug?.("sending file consent card", { uploadId, fileName, size: media.buffer.length });
213
-
214
- const messageId = await sendProactiveActivity({
215
- adapter,
216
- appId,
217
- ref,
218
- activity,
219
- errorPrefix: "msteams consent card send",
220
- });
221
-
222
- // Store the activity ID so the accept handler can replace the consent
223
- // card in-place. Mirror it into the FS store too because the invoke
224
- // callback may be delivered to a different process than the CLI send.
225
- setPendingUploadActivityId(uploadId, messageId);
226
- await setPendingUploadActivityIdFs(uploadId, messageId);
227
-
228
- log.info("sent file consent card", { conversationId, messageId, uploadId });
229
-
230
- return createMSTeamsSendResult({
231
- messageId,
232
- conversationId,
233
- kind: "card",
234
- pendingUploadId: uploadId,
235
- });
236
- }
237
-
238
- // Personal chat with small image: use base64 (only works for images)
239
- if (conversationType === "personal") {
240
- // Small image in personal chat: use base64 (only works for images)
241
- const base64 = media.buffer.toString("base64");
242
- const finalMediaUrl = `data:${media.contentType};base64,${base64}`;
243
-
244
- return sendTextWithMedia(ctx, messageText, finalMediaUrl);
245
- }
246
-
247
- if (isImage && !sharePointSiteId) {
248
- // Group chat/channel without SharePoint: send image inline (avoids OneDrive failures)
249
- const base64 = media.buffer.toString("base64");
250
- const finalMediaUrl = `data:${media.contentType};base64,${base64}`;
251
- return sendTextWithMedia(ctx, messageText, finalMediaUrl);
252
- }
253
-
254
- // Group chat or channel: upload to SharePoint (if siteId configured) or OneDrive
255
- try {
256
- if (sharePointSiteId) {
257
- // Use SharePoint upload + Graph API for native file card
258
- log.debug?.("uploading to SharePoint for native file card", {
259
- fileName,
260
- conversationType,
261
- siteId: sharePointSiteId,
262
- });
263
-
264
- const uploaded = await uploadAndShareSharePoint({
265
- buffer: media.buffer,
266
- filename: fileName,
267
- contentType: media.contentType,
268
- tokenProvider,
269
- siteId: sharePointSiteId,
270
- // Use the Graph-native chat ID (19:xxx format) — the Bot Framework conversationId
271
- // for personal DMs uses a different format that Graph API rejects.
272
- chatId: ctx.graphChatId ?? conversationId,
273
- usePerUserSharing: conversationType === "groupChat",
274
- });
275
-
276
- log.debug?.("SharePoint upload complete", {
277
- itemId: uploaded.itemId,
278
- shareUrl: uploaded.shareUrl,
279
- });
280
-
281
- // Get driveItem properties needed for native file card
282
- const driveItem = await getDriveItemProperties({
283
- siteId: sharePointSiteId,
284
- itemId: uploaded.itemId,
285
- tokenProvider,
286
- });
287
-
288
- log.debug?.("driveItem properties retrieved", {
289
- eTag: driveItem.eTag,
290
- webDavUrl: driveItem.webDavUrl,
291
- });
292
-
293
- // Build native Teams file card attachment and send via Bot Framework
294
- const fileCardAttachment = buildTeamsFileInfoCard(driveItem);
295
- const activity = {
296
- type: "message",
297
- text: messageText || undefined,
298
- attachments: [fileCardAttachment],
299
- };
300
- const messageId = await sendProactiveActivityRaw({
301
- adapter,
302
- appId,
303
- ref,
304
- activity,
305
- });
306
-
307
- log.info("sent native file card", {
308
- conversationId,
309
- messageId,
310
- fileName: driveItem.name,
311
- });
312
-
313
- return createMSTeamsSendResult({
314
- messageId,
315
- conversationId,
316
- kind: "media",
317
- });
318
- }
319
-
320
- // Fallback: no SharePoint site configured, use OneDrive with markdown link
321
- log.debug?.("uploading to OneDrive (no SharePoint site configured)", {
322
- fileName,
323
- conversationType,
324
- });
325
-
326
- const uploaded = await uploadAndShareOneDrive({
327
- buffer: media.buffer,
328
- filename: fileName,
329
- contentType: media.contentType,
330
- tokenProvider,
331
- });
332
-
333
- log.debug?.("OneDrive upload complete", {
334
- itemId: uploaded.itemId,
335
- shareUrl: uploaded.shareUrl,
336
- });
337
-
338
- // Send message with file link (Bot Framework doesn't support "reference" attachment type for sending)
339
- const fileLink = `📎 [${uploaded.name}](${uploaded.shareUrl})`;
340
- const activity = {
341
- type: "message",
342
- text: messageText ? `${messageText}\n\n${fileLink}` : fileLink,
343
- };
344
- const messageId = await sendProactiveActivityRaw({
345
- adapter,
346
- appId,
347
- ref,
348
- activity,
349
- });
350
-
351
- log.info("sent message with OneDrive file link", {
352
- conversationId,
353
- messageId,
354
- shareUrl: uploaded.shareUrl,
355
- });
356
-
357
- return createMSTeamsSendResult({
358
- messageId,
359
- conversationId,
360
- kind: "media",
361
- });
362
- } catch (err) {
363
- const classification = classifyMSTeamsSendError(err);
364
- const hint = formatMSTeamsSendErrorHint(classification);
365
- const status = classification.statusCode ? ` (HTTP ${classification.statusCode})` : "";
366
- throw new Error(
367
- `msteams file send failed${status}: ${formatUnknownError(err)}${hint ? ` (${hint})` : ""}`,
368
- { cause: err },
369
- );
370
- }
371
- }
372
-
373
- // No media: send text only
374
- return sendTextWithMedia(ctx, messageText, undefined);
375
- }
376
-
377
- /**
378
- * Send a text message with optional base64 media URL.
379
- */
380
- async function sendTextWithMedia(
381
- ctx: MSTeamsProactiveContext,
382
- text: string,
383
- mediaUrl: string | undefined,
384
- ): Promise<SendMSTeamsMessageResult> {
385
- const {
386
- adapter,
387
- appId,
388
- conversationId,
389
- ref,
390
- log,
391
- tokenProvider,
392
- sharePointSiteId,
393
- mediaMaxBytes,
394
- replyStyle,
395
- } = ctx;
396
-
397
- let platformMessageIds: string[];
398
- try {
399
- platformMessageIds = await sendMSTeamsMessages({
400
- replyStyle,
401
- adapter,
402
- appId,
403
- conversationRef: ref,
404
- messages: [{ text: text || undefined, mediaUrl }],
405
- retry: {},
406
- onRetry: (event) => {
407
- log.debug?.("retrying send", { conversationId, ...event });
408
- },
409
- tokenProvider,
410
- sharePointSiteId,
411
- mediaMaxBytes,
412
- });
413
- } catch (err) {
414
- const classification = classifyMSTeamsSendError(err);
415
- const hint = formatMSTeamsSendErrorHint(classification);
416
- const status = classification.statusCode ? ` (HTTP ${classification.statusCode})` : "";
417
- throw new Error(
418
- `msteams send failed${status}: ${formatUnknownError(err)}${hint ? ` (${hint})` : ""}`,
419
- { cause: err },
420
- );
421
- }
422
-
423
- const messageId = platformMessageIds[0] ?? "unknown";
424
- log.info("sent proactive message", { conversationId, messageId });
425
-
426
- return {
427
- messageId,
428
- conversationId,
429
- receipt: createMSTeamsSendReceipt({
430
- conversationId,
431
- platformMessageIds,
432
- kind: mediaUrl ? "media" : "text",
433
- }),
434
- };
435
- }
436
-
437
- type ProactiveActivityParams = {
438
- adapter: MSTeamsProactiveContext["adapter"];
439
- appId: string;
440
- ref: MSTeamsProactiveContext["ref"];
441
- activity: Record<string, unknown>;
442
- errorPrefix: string;
443
- };
444
-
445
- type ProactiveActivityRawParams = Omit<ProactiveActivityParams, "errorPrefix">;
446
-
447
- async function sendProactiveActivityRaw({
448
- adapter,
449
- appId,
450
- ref,
451
- activity,
452
- }: ProactiveActivityRawParams): Promise<string> {
453
- const baseRef = buildConversationReference(ref);
454
- const proactiveRef = {
455
- ...baseRef,
456
- activityId: undefined,
457
- };
458
-
459
- let messageId = "unknown";
460
- await adapter.continueConversation(appId, proactiveRef, async (ctx) => {
461
- const response = await ctx.sendActivity(activity);
462
- messageId = extractMessageId(response) ?? "unknown";
463
- });
464
- return messageId;
465
- }
466
-
467
- async function sendProactiveActivity({
468
- adapter,
469
- appId,
470
- ref,
471
- activity,
472
- errorPrefix,
473
- }: ProactiveActivityParams): Promise<string> {
474
- try {
475
- return await sendProactiveActivityRaw({
476
- adapter,
477
- appId,
478
- ref,
479
- activity,
480
- });
481
- } catch (err) {
482
- const classification = classifyMSTeamsSendError(err);
483
- const hint = formatMSTeamsSendErrorHint(classification);
484
- const status = classification.statusCode ? ` (HTTP ${classification.statusCode})` : "";
485
- throw new Error(
486
- `${errorPrefix} failed${status}: ${formatUnknownError(err)}${hint ? ` (${hint})` : ""}`,
487
- { cause: err },
488
- );
489
- }
490
- }
491
-
492
- /**
493
- * Send a poll (Adaptive Card) to a Teams conversation or user.
494
- */
495
- export async function sendPollMSTeams(
496
- params: SendMSTeamsPollParams,
497
- ): Promise<SendMSTeamsPollResult> {
498
- const { cfg, to, question, options, maxSelections } = params;
499
- const { adapter, appId, conversationId, ref, log } = await resolveMSTeamsSendContext({
500
- cfg,
501
- to,
502
- });
503
-
504
- const pollCard = buildMSTeamsPollCard({
505
- question,
506
- options,
507
- maxSelections,
508
- });
509
-
510
- log.debug?.("sending poll", {
511
- conversationId,
512
- pollId: pollCard.pollId,
513
- optionCount: pollCard.options.length,
514
- });
515
-
516
- const activity = {
517
- type: "message",
518
- attachments: [
519
- {
520
- contentType: "application/vnd.microsoft.card.adaptive",
521
- content: pollCard.card,
522
- },
523
- ],
524
- };
525
-
526
- // Send poll via proactive conversation (Adaptive Cards require direct activity send)
527
- const messageId = await sendProactiveActivity({
528
- adapter,
529
- appId,
530
- ref,
531
- activity,
532
- errorPrefix: "msteams poll send",
533
- });
534
-
535
- log.info("sent poll", { conversationId, pollId: pollCard.pollId, messageId });
536
-
537
- return {
538
- pollId: pollCard.pollId,
539
- messageId,
540
- conversationId,
541
- };
542
- }
543
-
544
- /**
545
- * Send an arbitrary Adaptive Card to a Teams conversation or user.
546
- */
547
- export async function sendAdaptiveCardMSTeams(
548
- params: SendMSTeamsCardParams,
549
- ): Promise<SendMSTeamsCardResult> {
550
- const { cfg, to, card } = params;
551
- const { adapter, appId, conversationId, ref, log } = await resolveMSTeamsSendContext({
552
- cfg,
553
- to,
554
- });
555
-
556
- log.debug?.("sending adaptive card", {
557
- conversationId,
558
- cardType: card.type,
559
- cardVersion: card.version,
560
- });
561
-
562
- const activity = {
563
- type: "message",
564
- attachments: [
565
- {
566
- contentType: "application/vnd.microsoft.card.adaptive",
567
- content: card,
568
- },
569
- ],
570
- };
571
-
572
- // Send card via proactive conversation
573
- const messageId = await sendProactiveActivity({
574
- adapter,
575
- appId,
576
- ref,
577
- activity,
578
- errorPrefix: "msteams card send",
579
- });
580
-
581
- log.info("sent adaptive card", { conversationId, messageId });
582
-
583
- return {
584
- messageId,
585
- conversationId,
586
- };
587
- }
588
-
589
- type EditMSTeamsMessageParams = {
590
- /** Full config (for credentials) */
591
- cfg: KlawConfig;
592
- /** Conversation ID or user ID */
593
- to: string;
594
- /** Activity ID of the message to edit */
595
- activityId: string;
596
- /** New message text */
597
- text: string;
598
- };
599
-
600
- type EditMSTeamsMessageResult = {
601
- conversationId: string;
602
- };
603
-
604
- type DeleteMSTeamsMessageParams = {
605
- /** Full config (for credentials) */
606
- cfg: KlawConfig;
607
- /** Conversation ID or user ID */
608
- to: string;
609
- /** Activity ID of the message to delete */
610
- activityId: string;
611
- };
612
-
613
- type DeleteMSTeamsMessageResult = {
614
- conversationId: string;
615
- };
616
-
617
- /**
618
- * Edit (update) a previously sent message in a Teams conversation.
619
- *
620
- * Uses the Bot Framework `continueConversation` → `updateActivity` flow
621
- * for proactive edits outside of the original turn context.
622
- */
623
- export async function editMessageMSTeams(
624
- params: EditMSTeamsMessageParams,
625
- ): Promise<EditMSTeamsMessageResult> {
626
- const { cfg, to, activityId, text } = params;
627
- const { adapter, appId, conversationId, ref, log } = await resolveMSTeamsSendContext({
628
- cfg,
629
- to,
630
- });
631
-
632
- log.debug?.("editing proactive message", { conversationId, activityId, textLength: text.length });
633
-
634
- const baseRef = buildConversationReference(ref);
635
- const proactiveRef = { ...baseRef, activityId: undefined };
636
-
637
- try {
638
- await adapter.continueConversation(appId, proactiveRef, async (ctx) => {
639
- await ctx.updateActivity({
640
- type: "message",
641
- id: activityId,
642
- text,
643
- });
644
- });
645
- } catch (err) {
646
- const classification = classifyMSTeamsSendError(err);
647
- const hint = formatMSTeamsSendErrorHint(classification);
648
- const status = classification.statusCode ? ` (HTTP ${classification.statusCode})` : "";
649
- throw new Error(
650
- `msteams edit failed${status}: ${formatUnknownError(err)}${hint ? ` (${hint})` : ""}`,
651
- { cause: err },
652
- );
653
- }
654
-
655
- log.info("edited proactive message", { conversationId, activityId });
656
-
657
- return { conversationId };
658
- }
659
-
660
- /**
661
- * Delete a previously sent message in a Teams conversation.
662
- *
663
- * Uses the Bot Framework `continueConversation` → `deleteActivity` flow
664
- * for proactive deletes outside of the original turn context.
665
- */
666
- export async function deleteMessageMSTeams(
667
- params: DeleteMSTeamsMessageParams,
668
- ): Promise<DeleteMSTeamsMessageResult> {
669
- const { cfg, to, activityId } = params;
670
- const { adapter, appId, conversationId, ref, log } = await resolveMSTeamsSendContext({
671
- cfg,
672
- to,
673
- });
674
-
675
- log.debug?.("deleting proactive message", { conversationId, activityId });
676
-
677
- const baseRef = buildConversationReference(ref);
678
- const proactiveRef = { ...baseRef, activityId: undefined };
679
-
680
- try {
681
- await adapter.continueConversation(appId, proactiveRef, async (ctx) => {
682
- await ctx.deleteActivity(activityId);
683
- });
684
- } catch (err) {
685
- const classification = classifyMSTeamsSendError(err);
686
- const hint = formatMSTeamsSendErrorHint(classification);
687
- const status = classification.statusCode ? ` (HTTP ${classification.statusCode})` : "";
688
- throw new Error(
689
- `msteams delete failed${status}: ${formatUnknownError(err)}${hint ? ` (${hint})` : ""}`,
690
- { cause: err },
691
- );
692
- }
693
-
694
- log.info("deleted proactive message", { conversationId, activityId });
695
-
696
- return { conversationId };
697
- }