fix(msteams): prevent duplicate text when stream exceeds 4000 char limit (#59297)

When a streamed response exceeds TEAMS_MAX_CHARS, the stream sets streamFailed=true and finalizes. Previously, hasContent returned false when streamFailed was true, causing preparePayload to pass through the full payload for block delivery, duplicating already-streamed text.

Now tracks streamed length and strips the already-delivered prefix from fallback payloads.

Fixes #58601

thanks @bradgroux
This commit is contained in:
Brad Groux
2026-04-01 19:03:19 -05:00
committed by GitHub
parent 560ea25294
commit 57949397fa
3 changed files with 77 additions and 4 deletions

View File

@@ -55,17 +55,44 @@ export function createTeamsReplyStreamController(params: {
},
preparePayload(payload: ReplyPayload): ReplyPayload | undefined {
if (!stream || !streamReceivedTokens || !stream.hasContent || stream.isFinalized) {
if (!stream || !streamReceivedTokens) {
return payload;
}
// Stream handled this text segment — finalize it and reset so any
const hasMedia = Boolean(payload.mediaUrl || payload.mediaUrls?.length);
// Stream failed after partial delivery (e.g. > 4000 chars). Send only
// the unstreamed suffix via block delivery to avoid duplicate text.
if (stream.isFailed) {
streamReceivedTokens = false;
if (!payload.text) {
return payload;
}
const streamedLength = stream.streamedLength;
if (streamedLength <= 0) {
return payload;
}
const remainingText = payload.text.slice(streamedLength);
if (!remainingText) {
return hasMedia ? { ...payload, text: undefined } : undefined;
}
return { ...payload, text: remainingText };
}
if (!stream.hasContent || stream.isFinalized) {
return payload;
}
// Stream handled this text segment. Finalize it and reset so any
// subsequent text segments (after tool calls) use fallback delivery.
// finalize() is idempotent; the later call in markDispatchIdle is a no-op.
streamReceivedTokens = false;
pendingFinalize = stream.finalize();
const hasMedia = Boolean(payload.mediaUrl || payload.mediaUrls?.length);
if (!hasMedia) {
return undefined;
}