UNPKG

@microsoft/teams.apps

Version:

<p> <a href="https://www.npmjs.com/package/@microsoft/teams.apps" target="_blank"> <img src="https://img.shields.io/npm/v/@microsoft/teams.apps/latest" /> </a> <a href="https://www.npmjs.com/package/@microsoft/teams.apps?activeTab=code

470 lines • 36.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.HttpStream = void 0; const axios_1 = require("axios"); const teams_api_1 = require("@microsoft/teams.api"); const teams_common_1 = require("@microsoft/teams.common"); const types_1 = require("../types"); const utils_1 = require("../utils"); /** * HTTP-based streaming implementation for Microsoft Teams activities. * * Allows sending typing indicators and messages in chunks to Teams. * Queues incoming activities and flushes them periodically to avoid * rate limits. * * Flow: * 1. `emit()` adds activities to the queue and starts a flush if none scheduled. * 2. `flush()` starts by cancelling any pending flush, then drains the entire queue under a lock. * 3. Informative typing updates are sent immediately, but only while no message text has accumulated. * 4. Message text is combined and sent as a typing activity. * 5. `flush()` schedules another flush 500ms later if more items remain in queue. * 6. `close()` waits for the queue to empty and sends the final message activity. */ class HttpStream { events = new teams_common_1.EventEmitter(); client; ref; replyChain; index = 0; id; text = ''; channelData = {}; finalActivity; queue = []; _result; _timeout; _logger; _flushing = false; _timedOut = false; _retired = false; _totalTimeout = 30000; // 30 seconds /** * @deprecated Teams no longer supports stopping a stream; always returns false. */ get canceled() { return false; } /** * Whether the stream has timed out. * Set when the service reports ten minutes have elapsed since the first * informative or content chunk. */ get timedOut() { return this._timedOut; } /** * Whether the current streamed message has been finalized. * * Closing is idempotent until the next emit or update reopens the stream. */ get closed() { return this._result !== undefined; } constructor(client, ref, logger, options) { this.client = client; this.ref = ref; this.replyChain = options?.replyChain; this._logger = logger?.child('stream') || new teams_common_1.ConsoleLogger('@teams/http-stream'); } emit(activity) { // Emitting after close or a retired Plan cycle starts a fresh stream cycle. if (this.closed || this._retired) { this._logger.debug('starting a new streamed message'); this.resetForNextStream(); } if (typeof activity === 'string') { activity = { type: 'message', text: activity, }; } else if (activity.type === 'message') { activity = teams_api_1.MessageActivityInput.from(activity); } else { activity = teams_api_1.TypingActivityInput.from(activity); } this.queue.push(activity); // Start flush if not already scheduled if (!this._timeout) { this.flush(); } } /** * Send a typing/status update without adding to the main text. * @param text Status text (ex. "Thinking...") * @param textFormat Format of `text` (ex. `'extendedmarkdown'`). Omit or pass `null` * to use the Teams default (`'markdown'`). */ update(text, textFormat) { this.emit({ type: 'typing', text: text, channelData: { streamType: 'informative' }, ...(textFormat ? { textFormat } : {}), }); } /** * Discard accumulated streamed text and any pending text deltas. The * final message sent by `close()` will not include them. Intermediate * typing chunks already shipped to Teams are not undone, but the final * message updates the same activity id, so the user sees the cleared * state. Pending non-message activities (typing updates, structured * message emits that follow) are not affected. */ clearText() { this.text = ''; // Drop queued text deltas so the next flush doesn't repopulate `text`. this.queue = this.queue.filter((a) => a.type !== 'message'); } /** * Close the stream by sending the final message. * Waits for all queued activities to flush. * A Plan timeout or final Plan conflict discards the failed cycle and raises once. * Further closes return undefined until the next emit or update starts a fresh cycle. */ async close() { // Closing is idempotent until the next emit/update reopens the stream. if (this.closed) { this._logger.debug('already closed'); return this._result; } if (this._retired) { this._logger.debug('failed stream cycle already retired, nothing to close'); return; } if (!this.index && !this.queue.length && !this._flushing) { this._logger.debug('closed with no content'); return; } // Wait until all queued activities are flushed const start = Date.now(); while (this.queue.length || !this.id || this._flushing) { if (Date.now() - start > this._totalTimeout) { this._logger.warn('Timeout while waiting for id and queue to flush'); return; } this._logger.debug('waiting for id to be set or queue to be empty'); await new Promise((resolve) => setTimeout(resolve, 100)); } if (!this.id) { this._logger.warn('no stream id set, cannot close stream'); return; } const finalAttachments = this.finalActivity?.attachments ?? []; const finalEntities = this.finalActivity?.entities ?? []; const finalSuggestedActions = this.finalActivity?.suggestedActions; const finalTextFormat = this.finalActivity?.textFormat; if (this.text === '' && !finalAttachments.length && !finalSuggestedActions) { this._logger.warn('no text, attachments, or suggested actions to send, cannot close stream'); return; } let res; if (this._timedOut) { // Streaming already tripped the 10-minute limit; update the original message in place. res = await this.sendFinal(); } else { // Build final message activity from the last-emitted message input (last wins), // overlaying accumulated text, id, channelData, and the stream-final entity. const activity = new teams_api_1.MessageActivityInput(this.text, { replyToId: this.finalActivity?.replyToId }) .withId(this.id) .addAttachments(...finalAttachments) .addEntities(...finalEntities) .withChannelData(this.channelData) .addStreamFinal(); if (finalTextFormat) { activity.withTextFormat(finalTextFormat); } if (finalSuggestedActions) { activity.withSuggestedActions(finalSuggestedActions); } try { res = await this.sendWithRetry(activity); } catch (err) { if (err instanceof types_1.StreamTimedOutError) { // The final streamed send tripped the 10-minute limit. Update the original // message in place with the buffered content: reuse the id and drop the // streamInfo entity + stream channel data so this routes to update, not create. res = await this.sendFinal(); } else { if (err instanceof types_1.PlanConflictStreamError && (0, teams_api_1.getPlanInfo)(activity)) { this.resetCurrentStream(); this._retired = true; } throw err; } } } this.events.emit('close', res); // Reset buffered message state; keep terminal status and the close result so // repeated close() calls stay idempotent until the next emit/update. this.index = 0; this.id = undefined; this.text = ''; this.channelData = {}; this.finalActivity = undefined; this._result = res; this._logger.debug(res); return res; } /** * Send through retry, excluding terminal stream errors and HTTP 409 Plan conflicts. * @param activity ActivityParams to send. */ async sendWithRetry(activity) { return utils_1.promises.retry(() => this.send(activity), { logger: this._logger, nonRetryable: [types_1.TerminalStreamError, types_1.PlanConflictStreamError], }); } /** * Send the buffered content as a plain final message. * * Drops the `streaminfo` entity (added by `addStreamFinal()` on the normal * close path) and the stream channel data so `id` is kept and the send routes * through the update path instead of creating a duplicate message. * Plan completion requires a POST: callers must resolve it through Plan.complete() * after a timeout instead of retrying this ordinary-message fallback. */ async sendFinal() { if ((0, teams_api_1.getPlanInfo)(this.finalActivity ?? {})) { this.resetCurrentStream(); this._retired = true; throw new types_1.StreamTimedOutError('Plan completion requires a POST; it cannot use the stream update fallback.'); } const finalAttachments = this.finalActivity?.attachments ?? []; const finalEntities = (this.finalActivity?.entities ?? []).filter((e) => e.type !== 'streaminfo'); const finalSuggestedActions = this.finalActivity?.suggestedActions; const finalTextFormat = this.finalActivity?.textFormat; const activity = { type: 'message', text: this.text, replyToId: this.finalActivity?.replyToId, attachments: finalAttachments, entities: finalEntities, }; if (this.id) { activity.id = this.id; } if (finalSuggestedActions) { activity.suggestedActions = finalSuggestedActions; } if (finalTextFormat) { activity.textFormat = finalTextFormat; } return this.sendWithRetry(activity); } /** * Prepare the stream instance to start a new stream cycle after close(). */ resetForNextStream() { this.resetCurrentStream(); this._result = undefined; this._timedOut = false; } /** * Flush queued activities. * Drains the entire queue in a single pass. */ async flush() { // if locked or no queue, return early if (!this.queue.length || this._flushing) return; this._flushing = true; try { if (this._timeout) { clearTimeout(this._timeout); this._timeout = undefined; } const informativeUpdates = []; const startLength = this.queue.length; while (this.queue.length) { const activity = this.queue.shift(); if (!activity) continue; if (activity.type === 'message') { if (activity.text) { this.text += activity.text; } // Last emitted message wins for attachments / entities / suggestedActions. // Matches the Python streamer's `_final_activity` behavior. this.finalActivity = activity; } if (activity.type === 'typing') { if (activity.channelData?.streamType === 'informative' && this.text === '') { informativeUpdates.push(activity); } } if (activity.channelData) { this.channelData = { ...this.channelData, ...activity.channelData, }; } } if (startLength === 0) return; // Once the stream has timed out, stop sending chunks for this cycle. if (this._timedOut) return; // Streamed text chunks use last-emitted-message-wins for textFormat (same as // attachments/entities/etc.), so they render like the final message. const textFormat = this.finalActivity?.textFormat; // Send informative updates immediately. Each carries its own textFormat // (finalActivity isn't set yet at this point), so read it off the update. for (const informativeUpdate of informativeUpdates) { const activity = new teams_api_1.TypingActivityInput() .withText(informativeUpdate.text || '') .withChannelData({ streamType: 'informative' }); if (informativeUpdate.textFormat) { activity.withTextFormat(informativeUpdate.textFormat); } await this.pushStreamChunk(activity); } if (this.text) { const activity = new teams_api_1.TypingActivityInput().withText(this.text); if (textFormat) { activity.withTextFormat(textFormat); } await this.pushStreamChunk(activity); } // Schedule another flush if queue is not empty if (this.queue.length) { this._timeout = setTimeout(this.flush.bind(this), 500); } } catch (err) { this._logger.error(err, 'flush failed'); } finally { this._flushing = false; } } /** * Push a new chunk to the stream. * @param activity Typing activity input to send. */ async pushStreamChunk(activity) { if (this.id) { activity.id = this.id; } if (!activity.channelData) { activity.channelData = {}; } if (!activity.channelData.streamId) { activity.channelData.streamId = activity.id; } if (!activity.channelData.streamType) { activity.channelData.streamType = 'streaming'; } activity.channelData.streamSequence = this.index + 1; if (!activity.entities) { activity.entities = []; } activity.entities.push({ type: 'streaminfo', streamId: activity.id, streamType: activity.channelData.streamType, streamSequence: activity.channelData.streamSequence, }); let res; try { res = await this.sendWithRetry(activity); } catch (err) { // A timed-out chunk is swallowed here; close() sends the buffered content as a // plain message once the stream has been marked timed out. if (err instanceof types_1.StreamTimedOutError) { return; } throw err; } this.events.emit('chunk', res); this.index++; if (!this.id) { this.id = res.id; } } /** * Creates the first message of the stream as a reply in the reply chain (thread). * In a channel, when the service answers HTTP 404 and the reply chain id is a plain non-zero number, creates the message in the inbound conversation instead. * Any other error is thrown, including a 404 outside a channel. */ async createInReplyChain(replyChain, payload) { try { const res = await this.client.conversations.createReplyChainActivity(replyChain.chatOrChannelId, replyChain.replyChainId, payload); return { ...payload, ...res }; } catch (err) { const id = replyChain.replyChainId; const inChannel = this.ref.conversation?.conversationType === 'channel'; if (!inChannel || err?.response?.status !== 404 || !/^\d+$/.test(id) || /^0+$/.test(id)) { throw err; } } const res = await this.client.conversations.createActivity(this.ref.conversation.id, payload); return { ...payload, ...res }; } /** * Send or update a streaming activity * @param activity ActivityParams to send. */ async send(activity) { const params = (0, teams_api_1.toActivityParams)(activity); const payload = { ...params, from: this.ref.bot, conversation: this.ref.conversation, replyToId: params.replyToId ?? this.ref.activityId, }; try { if (payload.id && !(payload.entities?.some((e) => e.type === 'streaminfo') || false)) { const res = await this.client.conversations.updateActivity(this.ref.conversation.id, payload.id, payload); return { ...payload, ...res }; } if (this.replyChain && !payload.id) { return await this.createInReplyChain(this.replyChain, payload); } const res = await this.client.conversations.createActivity(this.ref.conversation.id, payload); return { ...payload, ...res }; } catch (err) { if ((0, axios_1.isAxiosError)(err) && err.response?.status === 409 && err.response.data?.error?.code === 'PlanConflict') { throw new types_1.PlanConflictStreamError(err); } // Various error codes are used for streaming. // https://learn.microsoft.com/en-us/microsoftteams/platform/bots/streaming-ux?tabs=csharp#error-codes if (err?.response?.status === 403) { const message = err?.response?.data?.error?.message ?? ''; const normalized = message.toLowerCase(); if (normalized.includes('exceeded streaming time')) { this._timedOut = true; this._logger.warn('The bot failed to complete the streaming process within the time limit of ten minutes from the first informative or content chunk.'); throw new types_1.StreamTimedOutError(message); } else if (normalized.includes('not allowed') && !normalized.includes('completed streamed message')) { this._logger.warn('The streaming API isn\'t allowed for the user or bot.'); throw new types_1.StreamNotAllowedError(message); } this._logger.warn(`Teams returned a streaming error: ${message}`); throw new types_1.TerminalStreamError(message); } throw err; } } resetCurrentStream() { this._retired = false; this.index = 0; this.id = undefined; this.text = ''; this.channelData = {}; this.finalActivity = undefined; this.queue = []; } } exports.HttpStream = HttpStream; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaHR0cC1zdHJlYW0uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvaHR0cC9odHRwLXN0cmVhbS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSxpQ0FBcUM7QUFFckMsb0RBZThCO0FBQzlCLDBEQUErRTtBQUUvRSxvQ0FRa0I7QUFDbEIsb0NBQW9DO0FBRXBDOzs7Ozs7Ozs7Ozs7OztHQWNHO0FBQ0gsTUFBYSxVQUFVO0lBQ1osTUFBTSxHQUFHLElBQUksMkJBQVksRUFBbUIsQ0FBQztJQUU1QyxNQUFNLENBQVM7SUFDZixHQUFHLENBQXdCO0lBQzNCLFVBQVUsQ0FBK0I7SUFDekMsS0FBSyxHQUFHLENBQUMsQ0FBQztJQUNWLEVBQUUsQ0FBVTtJQUNaLElBQUksR0FBVyxFQUFFLENBQUM7SUFDbEIsV0FBVyxHQUFnQixFQUFFLENBQUM7SUFDOUIsYUFBYSxDQUF5QjtJQUN0QyxLQUFLLEdBQXdELEVBQUUsQ0FBQztJQUVsRSxPQUFPLENBQWdCO0lBQ3ZCLFFBQVEsQ0FBa0I7SUFDMUIsT0FBTyxDQUFVO0lBQ2pCLFNBQVMsR0FBWSxLQUFLLENBQUM7SUFDM0IsU0FBUyxHQUFZLEtBQUssQ0FBQztJQUMzQixRQUFRLEdBQVksS0FBSyxDQUFDO0lBQ2pCLGFBQWEsR0FBRyxLQUFLLENBQUMsQ0FBQyxhQUFhO0lBRXJEOztPQUVHO0lBQ0gsSUFBSSxRQUFRO1FBQ1YsT0FBTyxLQUFLLENBQUM7SUFDZixDQUFDO0lBRUQ7Ozs7T0FJRztJQUNILElBQUksUUFBUTtRQUNWLE9BQU8sSUFBSSxDQUFDLFNBQVMsQ0FBQztJQUN4QixDQUFDO0lBRUQ7Ozs7T0FJRztJQUNILElBQUksTUFBTTtRQUNSLE9BQU8sSUFBSSxDQUFDLE9BQU8sS0FBSyxTQUFTLENBQUM7SUFDcEMsQ0FBQztJQUVELFlBQ0UsTUFBYyxFQUNkLEdBQTBCLEVBQzFCLE1BQWdCLEVBQ2hCLE9BQXVCO1FBRXZCLElBQUksQ0FBQyxNQUFNLEdBQUcsTUFBTSxD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