+ ${reasoningMarkdown
+ ? html`
${unsafeHTML(
+ toSanitizedMarkdownHtml(reasoningMarkdown),
+ )}
`
+ : nothing}
${markdown
? html`
${unsafeHTML(toSanitizedMarkdownHtml(markdown))}
`
: nothing}
diff --git a/ui/src/ui/chat/message-normalizer.test.ts b/ui/src/ui/chat/message-normalizer.test.ts
index 7736ce1a2..132a4be17 100644
--- a/ui/src/ui/chat/message-normalizer.test.ts
+++ b/ui/src/ui/chat/message-normalizer.test.ts
@@ -103,25 +103,25 @@ describe("message-normalizer", () => {
});
describe("normalizeRoleForGrouping", () => {
- it("returns assistant for toolresult", () => {
- expect(normalizeRoleForGrouping("toolresult")).toBe("assistant");
- expect(normalizeRoleForGrouping("toolResult")).toBe("assistant");
- expect(normalizeRoleForGrouping("TOOLRESULT")).toBe("assistant");
+ it("returns tool for toolresult", () => {
+ expect(normalizeRoleForGrouping("toolresult")).toBe("tool");
+ expect(normalizeRoleForGrouping("toolResult")).toBe("tool");
+ expect(normalizeRoleForGrouping("TOOLRESULT")).toBe("tool");
});
- it("returns assistant for tool_result", () => {
- expect(normalizeRoleForGrouping("tool_result")).toBe("assistant");
- expect(normalizeRoleForGrouping("TOOL_RESULT")).toBe("assistant");
+ it("returns tool for tool_result", () => {
+ expect(normalizeRoleForGrouping("tool_result")).toBe("tool");
+ expect(normalizeRoleForGrouping("TOOL_RESULT")).toBe("tool");
});
- it("returns assistant for tool", () => {
- expect(normalizeRoleForGrouping("tool")).toBe("assistant");
- expect(normalizeRoleForGrouping("Tool")).toBe("assistant");
+ it("returns tool for tool", () => {
+ expect(normalizeRoleForGrouping("tool")).toBe("tool");
+ expect(normalizeRoleForGrouping("Tool")).toBe("tool");
});
- it("returns assistant for function", () => {
- expect(normalizeRoleForGrouping("function")).toBe("assistant");
- expect(normalizeRoleForGrouping("Function")).toBe("assistant");
+ it("returns tool for function", () => {
+ expect(normalizeRoleForGrouping("function")).toBe("tool");
+ expect(normalizeRoleForGrouping("Function")).toBe("tool");
});
it("preserves user role", () => {
diff --git a/ui/src/ui/chat/message-normalizer.ts b/ui/src/ui/chat/message-normalizer.ts
index dfc861fd5..aa7b39d5c 100644
--- a/ui/src/ui/chat/message-normalizer.ts
+++ b/ui/src/ui/chat/message-normalizer.ts
@@ -14,8 +14,36 @@ export function normalizeMessage(message: unknown): NormalizedMessage {
const m = message as Record
;
let role = typeof m.role === "string" ? m.role : "unknown";
- // Detect tool result messages by presence of toolCallId or tool_call_id
- if (typeof m.toolCallId === "string" || typeof m.tool_call_id === "string") {
+ // Detect tool messages by common gateway shapes.
+ // Some tool events come through as assistant role with tool_* items in the content array.
+ const hasToolId =
+ typeof m.toolCallId === "string" || typeof m.tool_call_id === "string";
+
+ const contentRaw = m.content;
+ const contentItems = Array.isArray(contentRaw) ? contentRaw : null;
+ const hasToolContent =
+ Array.isArray(contentItems) &&
+ contentItems.some((item) => {
+ const x = item as Record;
+ const t = String(x.type ?? "").toLowerCase();
+ return (
+ t === "toolcall" ||
+ t === "tool_call" ||
+ t === "tooluse" ||
+ t === "tool_use" ||
+ t === "toolresult" ||
+ t === "tool_result" ||
+ t === "tool_call" ||
+ t === "tool_result" ||
+ (typeof x.name === "string" && x.arguments != null)
+ );
+ });
+
+ const hasToolName =
+ typeof (m as Record).toolName === "string" ||
+ typeof (m as Record).tool_name === "string";
+
+ if (hasToolId || hasToolContent || hasToolName) {
role = "toolResult";
}
@@ -43,19 +71,22 @@ export function normalizeMessage(message: unknown): NormalizedMessage {
/**
* Normalize role for grouping purposes.
- * Tool results should be grouped with assistant messages.
*/
export function normalizeRoleForGrouping(role: string): string {
const lower = role.toLowerCase();
- // All tool-related roles should display as assistant
+ // Keep tool-related roles distinct so the UI can style/toggle them.
if (
lower === "toolresult" ||
lower === "tool_result" ||
lower === "tool" ||
- lower === "function"
+ lower === "function" ||
+ lower === "toolresult"
) {
- return "assistant";
+ return "tool";
}
+ if (lower === "assistant") return "assistant";
+ if (lower === "user") return "user";
+ if (lower === "system") return "system";
return role;
}
diff --git a/ui/src/ui/storage.ts b/ui/src/ui/storage.ts
index ce755ccea..1288895d9 100644
--- a/ui/src/ui/storage.ts
+++ b/ui/src/ui/storage.ts
@@ -9,6 +9,7 @@ export type UiSettings = {
lastActiveSessionKey: string;
theme: ThemeMode;
chatFocusMode: boolean;
+ chatShowThinking: boolean;
splitRatio: number; // Sidebar split ratio (0.4 to 0.7, default 0.6)
useNewChatLayout: boolean; // Slack-style grouped messages layout
navCollapsed: boolean; // Collapsible sidebar state
@@ -28,6 +29,7 @@ export function loadSettings(): UiSettings {
lastActiveSessionKey: "main",
theme: "system",
chatFocusMode: false,
+ chatShowThinking: true,
splitRatio: 0.6,
useNewChatLayout: true, // Enabled by default
navCollapsed: false,
@@ -65,6 +67,10 @@ export function loadSettings(): UiSettings {
typeof parsed.chatFocusMode === "boolean"
? parsed.chatFocusMode
: defaults.chatFocusMode,
+ chatShowThinking:
+ typeof parsed.chatShowThinking === "boolean"
+ ? parsed.chatShowThinking
+ : defaults.chatShowThinking,
splitRatio:
typeof parsed.splitRatio === "number" &&
parsed.splitRatio >= 0.4 &&
diff --git a/ui/src/ui/views/chat.ts b/ui/src/ui/views/chat.ts
index 2b6fb5a3f..f25221db5 100644
--- a/ui/src/ui/views/chat.ts
+++ b/ui/src/ui/views/chat.ts
@@ -21,6 +21,7 @@ export type ChatProps = {
sessionKey: string;
onSessionKeyChange: (next: string) => void;
thinkingLevel: string | null;
+ showThinking: boolean;
loading: boolean;
sending: boolean;
canAbort?: boolean;
@@ -69,7 +70,7 @@ export function renderChat(props: ChatProps) {
(row) => row.key === props.sessionKey,
);
const reasoningLevel = activeSession?.reasoningLevel ?? "off";
- const showReasoning = reasoningLevel !== "off";
+ const showReasoning = props.showThinking && reasoningLevel !== "off";
const composePlaceholder = props.connected
? "Message (↩ to send, Shift+↩ for line breaks)"
@@ -310,18 +311,27 @@ function buildChatItems(props: ChatProps): Array {
});
}
for (let i = historyStart; i < history.length; i++) {
+ const msg = history[i];
+ const normalized = normalizeMessage(msg);
+
+ if (!props.showThinking && normalized.role.toLowerCase() === "toolresult") {
+ continue;
+ }
+
items.push({
kind: "message",
- key: messageKey(history[i], i),
- message: history[i],
+ key: messageKey(msg, i),
+ message: msg,
});
}
- for (let i = 0; i < tools.length; i++) {
- items.push({
- kind: "message",
- key: messageKey(tools[i], i + history.length),
- message: tools[i],
- });
+ if (props.showThinking) {
+ for (let i = 0; i < tools.length; i++) {
+ items.push({
+ kind: "message",
+ key: messageKey(tools[i], i + history.length),
+ message: tools[i],
+ });
+ }
}
if (props.stream !== null) {