fix(discord): strip reasoning tags from partial stream preview

When streamMode is "partial", reasoning/thinking block content can leak
into the Discord draft preview because the partial text is forwarded to
the draft stream without filtering.  Apply `stripReasoningTagsFromText`
before updating the draft and skip pure-reasoning messages (those
starting with "Reasoning:\n") so internal thinking traces never reach
the user-visible preview.

Fixes #24532

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
justinhuangcode
2026-02-23 17:30:59 +00:00
committed by Peter Steinberger
parent 0ded77ca7d
commit e8a4d5d9bd

View File

@@ -30,6 +30,7 @@ import { convertMarkdownTables } from "../../markdown/tables.js";
import { buildAgentSessionKey } from "../../routing/resolve-route.js";
import { resolveThreadSessionKeys } from "../../routing/session-key.js";
import { buildUntrustedChannelMetadata } from "../../security/channel-metadata.js";
import { stripReasoningTagsFromText } from "../../shared/text/reasoning-tags.js";
import { truncateUtf16Safe } from "../../utils.js";
import { chunkDiscordTextWithMode } from "../chunk.js";
import { resolveDiscordDraftStreamingChunking } from "../draft-chunking.js";
@@ -485,7 +486,13 @@ export async function processDiscordMessage(ctx: DiscordMessagePreflightContext)
if (!draftStream || !text) {
return;
}
if (text === lastPartialText) {
// Strip reasoning/thinking tags that may leak through the stream.
const cleaned = stripReasoningTagsFromText(text, { mode: "strict", trim: "both" });
// Skip pure-reasoning messages (e.g. "Reasoning:\n…") that contain no answer text.
if (!cleaned || cleaned.startsWith("Reasoning:\n")) {
return;
}
if (cleaned === lastPartialText) {
return;
}
hasStreamedMessage = true;
@@ -493,30 +500,30 @@ export async function processDiscordMessage(ctx: DiscordMessagePreflightContext)
// Keep the longer preview to avoid visible punctuation flicker.
if (
lastPartialText &&
lastPartialText.startsWith(text) &&
text.length < lastPartialText.length
lastPartialText.startsWith(cleaned) &&
cleaned.length < lastPartialText.length
) {
return;
}
lastPartialText = text;
draftStream.update(text);
lastPartialText = cleaned;
draftStream.update(cleaned);
return;
}
let delta = text;
if (text.startsWith(lastPartialText)) {
delta = text.slice(lastPartialText.length);
let delta = cleaned;
if (cleaned.startsWith(lastPartialText)) {
delta = cleaned.slice(lastPartialText.length);
} else {
// Streaming buffer reset (or non-monotonic stream). Start fresh.
draftChunker?.reset();
draftText = "";
}
lastPartialText = text;
lastPartialText = cleaned;
if (!delta) {
return;
}
if (!draftChunker) {
draftText = text;
draftText = cleaned;
draftStream.update(draftText);
return;
}