diff --git a/frontend/src/components/ai-elements/reasoning.tsx b/frontend/src/components/ai-elements/reasoning.tsx
index 8c9d5b206..5f1d2321e 100644
--- a/frontend/src/components/ai-elements/reasoning.tsx
+++ b/frontend/src/components/ai-elements/reasoning.tsx
@@ -11,6 +11,7 @@ import { BrainIcon, ChevronDownIcon } from "lucide-react";
import type { ComponentProps, ReactNode } from "react";
import { createContext, memo, useContext, useEffect, useState } from "react";
import { Streamdown } from "streamdown";
+import { reasoningPlugins } from "@/core/streamdown/plugins";
import { Shimmer } from "./shimmer";
type ReasoningContextValue = {
@@ -177,7 +178,7 @@ export const ReasoningContent = memo(
)}
{...props}
>
- {children}
+ {children}
),
);
diff --git a/frontend/src/core/streamdown/plugins.ts b/frontend/src/core/streamdown/plugins.ts
index e9214031b..d576252c5 100644
--- a/frontend/src/core/streamdown/plugins.ts
+++ b/frontend/src/core/streamdown/plugins.ts
@@ -28,6 +28,15 @@ export const streamdownPluginsWithWordAnimation = {
] as StreamdownProps["rehypePlugins"],
};
+// Plugins for reasoning/thinking content — derived from streamdownPlugins but without rehypeRaw,
+// to prevent LLM-hallucinated HTML tags (e.g. ) from being rendered as DOM elements.
+export const reasoningPlugins = {
+ remarkPlugins: streamdownPlugins.remarkPlugins,
+ rehypePlugins: streamdownPlugins.rehypePlugins?.filter(
+ (p) => p !== rehypeRaw,
+ ) as StreamdownProps["rehypePlugins"],
+};
+
// Plugins for human messages - no autolink to prevent URL bleeding into adjacent text
export const humanMessagePlugins = {
remarkPlugins: [
diff --git a/frontend/tests/unit/core/streamdown/plugins.test.ts b/frontend/tests/unit/core/streamdown/plugins.test.ts
new file mode 100644
index 000000000..efe0ab719
--- /dev/null
+++ b/frontend/tests/unit/core/streamdown/plugins.test.ts
@@ -0,0 +1,13 @@
+import rehypeRaw from "rehype-raw";
+import { expect, test } from "vitest";
+
+import { reasoningPlugins, streamdownPlugins } from "@/core/streamdown/plugins";
+
+test("streamdownPlugins includes rehypeRaw", () => {
+ expect(streamdownPlugins.rehypePlugins).toContain(rehypeRaw);
+});
+
+test("reasoningPlugins does not include rehypeRaw", () => {
+ const flat = reasoningPlugins.rehypePlugins?.flat();
+ expect(flat).not.toContain(rehypeRaw);
+});