@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
JavaScript
"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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