Skip to content

Commit f62a054

Browse files
committed
perf(ui): split chat role normalization imports
1 parent 265b97b commit f62a054

9 files changed

Lines changed: 85 additions & 115 deletions

ui/src/ui/chat/build-chat-items.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { ChatItem, MessageGroup, ToolCard } from "../types/chat-types.ts";
22
import { extractTextCached } from "./message-extract.ts";
3-
import { normalizeMessage, normalizeRoleForGrouping } from "./message-normalizer.ts";
3+
import { normalizeMessage } from "./message-normalizer.ts";
4+
import { normalizeRoleForGrouping } from "./role-normalizer.ts";
45
import { messageMatchesSearchQuery } from "./search-match.ts";
56
import { extractToolCards, extractToolPreview } from "./tool-cards.ts";
67

ui/src/ui/chat/chat-avatar.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
isRenderableControlUiAvatarUrl,
1111
resolveAssistantTextAvatar,
1212
} from "../views/agents-utils.ts";
13-
import { normalizeRoleForGrouping } from "./message-normalizer.ts";
13+
import { normalizeRoleForGrouping } from "./role-normalizer.ts";
1414

1515
export function renderChatAvatar(
1616
role: string,

ui/src/ui/chat/grouped-render.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,8 @@ import {
2424
extractThinkingCached,
2525
formatReasoningMarkdown,
2626
} from "./message-extract.ts";
27-
import {
28-
isToolResultMessage,
29-
normalizeMessage,
30-
normalizeRoleForGrouping,
31-
} from "./message-normalizer.ts";
27+
import { isToolResultMessage, normalizeMessage } from "./message-normalizer.ts";
28+
import { normalizeRoleForGrouping } from "./role-normalizer.ts";
3229
import { isTtsSupported, speakText, stopTts, isTtsSpeaking } from "./speech.ts";
3330
import {
3431
extractToolCards,

ui/src/ui/chat/message-normalizer.test.ts

Lines changed: 1 addition & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2-
import {
3-
normalizeMessage,
4-
normalizeRoleForGrouping,
5-
isToolResultMessage,
6-
} from "./message-normalizer.ts";
2+
import { normalizeMessage } from "./message-normalizer.ts";
73

84
describe("message-normalizer", () => {
95
describe("normalizeMessage", () => {
@@ -390,69 +386,4 @@ describe("message-normalizer", () => {
390386
expect(result.senderLabel).toBe("Iris");
391387
});
392388
});
393-
394-
describe("normalizeRoleForGrouping", () => {
395-
it("returns tool for toolresult", () => {
396-
expect(normalizeRoleForGrouping("toolresult")).toBe("tool");
397-
expect(normalizeRoleForGrouping("toolResult")).toBe("tool");
398-
expect(normalizeRoleForGrouping("TOOLRESULT")).toBe("tool");
399-
});
400-
401-
it("returns tool for tool_result", () => {
402-
expect(normalizeRoleForGrouping("tool_result")).toBe("tool");
403-
expect(normalizeRoleForGrouping("TOOL_RESULT")).toBe("tool");
404-
});
405-
406-
it("returns tool for tool", () => {
407-
expect(normalizeRoleForGrouping("tool")).toBe("tool");
408-
expect(normalizeRoleForGrouping("Tool")).toBe("tool");
409-
});
410-
411-
it("returns tool for function", () => {
412-
expect(normalizeRoleForGrouping("function")).toBe("tool");
413-
expect(normalizeRoleForGrouping("Function")).toBe("tool");
414-
});
415-
416-
it("preserves user role", () => {
417-
expect(normalizeRoleForGrouping("user")).toBe("user");
418-
expect(normalizeRoleForGrouping("User")).toBe("User");
419-
});
420-
421-
it("preserves assistant role", () => {
422-
expect(normalizeRoleForGrouping("assistant")).toBe("assistant");
423-
});
424-
425-
it("preserves system role", () => {
426-
expect(normalizeRoleForGrouping("system")).toBe("system");
427-
});
428-
});
429-
430-
describe("isToolResultMessage", () => {
431-
it("returns true for toolresult role", () => {
432-
expect(isToolResultMessage({ role: "toolresult" })).toBe(true);
433-
expect(isToolResultMessage({ role: "toolResult" })).toBe(true);
434-
expect(isToolResultMessage({ role: "TOOLRESULT" })).toBe(true);
435-
});
436-
437-
it("returns true for tool_result role", () => {
438-
expect(isToolResultMessage({ role: "tool_result" })).toBe(true);
439-
expect(isToolResultMessage({ role: "TOOL_RESULT" })).toBe(true);
440-
});
441-
442-
it("returns false for other roles", () => {
443-
expect(isToolResultMessage({ role: "user" })).toBe(false);
444-
expect(isToolResultMessage({ role: "assistant" })).toBe(false);
445-
expect(isToolResultMessage({ role: "tool" })).toBe(false);
446-
});
447-
448-
it("returns false for missing role", () => {
449-
expect(isToolResultMessage({})).toBe(false);
450-
expect(isToolResultMessage({ content: "test" })).toBe(false);
451-
});
452-
453-
it("returns false for non-string role", () => {
454-
expect(isToolResultMessage({ role: 123 })).toBe(false);
455-
expect(isToolResultMessage({ role: null })).toBe(false);
456-
});
457-
});
458389
});

ui/src/ui/chat/message-normalizer.ts

Lines changed: 1 addition & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { mediaKindFromMime } from "../../../../src/media/constants.js";
1313
import { splitMediaFromOutput } from "../../../../src/media/parse.js";
1414
import { parseInlineDirectives } from "../../../../src/utils/directive-tags.js";
1515
import type { NormalizedMessage, MessageContentItem } from "../types/chat-types.ts";
16+
export { isToolResultMessage, normalizeRoleForGrouping } from "./role-normalizer.ts";
1617

1718
function coerceCanvasPreview(
1819
value: unknown,
@@ -389,39 +390,3 @@ export function normalizeMessage(message: unknown): NormalizedMessage {
389390
...(replyTarget ? { replyTarget } : {}),
390391
};
391392
}
392-
393-
/**
394-
* Normalize role for grouping purposes.
395-
*/
396-
export function normalizeRoleForGrouping(role: string): string {
397-
const lower = role.toLowerCase();
398-
// Preserve original casing when it's already a core role.
399-
if (role === "user" || role === "User") {
400-
return role;
401-
}
402-
if (role === "assistant") {
403-
return "assistant";
404-
}
405-
if (role === "system") {
406-
return "system";
407-
}
408-
// Keep tool-related roles distinct so the UI can style/toggle them.
409-
if (
410-
lower === "toolresult" ||
411-
lower === "tool_result" ||
412-
lower === "tool" ||
413-
lower === "function"
414-
) {
415-
return "tool";
416-
}
417-
return role;
418-
}
419-
420-
/**
421-
* Check if a message is a tool result message based on its role.
422-
*/
423-
export function isToolResultMessage(message: unknown): boolean {
424-
const m = message as Record<string, unknown>;
425-
const role = typeof m.role === "string" ? m.role.toLowerCase() : "";
426-
return role === "toolresult" || role === "tool_result";
427-
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { describe, expect, it } from "vitest";
2+
import { isToolResultMessage, normalizeRoleForGrouping } from "./role-normalizer.ts";
3+
4+
describe("normalizeRoleForGrouping", () => {
5+
it("returns tool for tool result role variants", () => {
6+
expect(normalizeRoleForGrouping("toolresult")).toBe("tool");
7+
expect(normalizeRoleForGrouping("toolResult")).toBe("tool");
8+
expect(normalizeRoleForGrouping("TOOLRESULT")).toBe("tool");
9+
expect(normalizeRoleForGrouping("tool_result")).toBe("tool");
10+
expect(normalizeRoleForGrouping("TOOL_RESULT")).toBe("tool");
11+
});
12+
13+
it("returns tool for tool and function roles", () => {
14+
expect(normalizeRoleForGrouping("tool")).toBe("tool");
15+
expect(normalizeRoleForGrouping("Tool")).toBe("tool");
16+
expect(normalizeRoleForGrouping("function")).toBe("tool");
17+
expect(normalizeRoleForGrouping("Function")).toBe("tool");
18+
});
19+
20+
it("preserves core roles", () => {
21+
expect(normalizeRoleForGrouping("user")).toBe("user");
22+
expect(normalizeRoleForGrouping("User")).toBe("User");
23+
expect(normalizeRoleForGrouping("assistant")).toBe("assistant");
24+
expect(normalizeRoleForGrouping("system")).toBe("system");
25+
});
26+
27+
it("detects only tool result role variants", () => {
28+
expect(isToolResultMessage({ role: "toolresult" })).toBe(true);
29+
expect(isToolResultMessage({ role: "toolResult" })).toBe(true);
30+
expect(isToolResultMessage({ role: "TOOLRESULT" })).toBe(true);
31+
expect(isToolResultMessage({ role: "tool_result" })).toBe(true);
32+
expect(isToolResultMessage({ role: "TOOL_RESULT" })).toBe(true);
33+
expect(isToolResultMessage({ role: "user" })).toBe(false);
34+
expect(isToolResultMessage({ role: "assistant" })).toBe(false);
35+
expect(isToolResultMessage({ role: "tool" })).toBe(false);
36+
expect(isToolResultMessage({})).toBe(false);
37+
expect(isToolResultMessage({ content: "test" })).toBe(false);
38+
expect(isToolResultMessage({ role: 123 })).toBe(false);
39+
expect(isToolResultMessage({ role: null })).toBe(false);
40+
});
41+
});

ui/src/ui/chat/role-normalizer.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/**
2+
* Normalize role for grouping purposes.
3+
*/
4+
export function normalizeRoleForGrouping(role: string): string {
5+
const lower = role.toLowerCase();
6+
// Preserve original casing when it's already a core role.
7+
if (role === "user" || role === "User") {
8+
return role;
9+
}
10+
if (role === "assistant") {
11+
return "assistant";
12+
}
13+
if (role === "system") {
14+
return "system";
15+
}
16+
// Keep tool-related roles distinct so the UI can style/toggle them.
17+
if (
18+
lower === "toolresult" ||
19+
lower === "tool_result" ||
20+
lower === "tool" ||
21+
lower === "function"
22+
) {
23+
return "tool";
24+
}
25+
return role;
26+
}
27+
28+
/**
29+
* Check if a message is a tool result message based on its role.
30+
*/
31+
export function isToolResultMessage(message: unknown): boolean {
32+
const m = message as Record<string, unknown>;
33+
const role = typeof m.role === "string" ? m.role.toLowerCase() : "";
34+
return role === "toolresult" || role === "tool_result";
35+
}

ui/src/ui/chat/tool-cards.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import type { SidebarContent } from "../sidebar-content.ts";
77
import { formatToolDetail, resolveToolDisplay } from "../tool-display.ts";
88
import type { ToolCard } from "../types/chat-types.ts";
99
import { extractTextCached } from "./message-extract.ts";
10-
import { isToolResultMessage } from "./message-normalizer.ts";
10+
import { isToolResultMessage } from "./role-normalizer.ts";
1111
import { formatToolOutputForSidebar, getTruncatedPreview } from "./tool-helpers.ts";
1212

1313
export type ToolPreview = NonNullable<ToolCard["preview"]>;

ui/src/ui/chat/tool-expansion-state.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { ChatItem, MessageGroup } from "../types/chat-types.ts";
2-
import { isToolResultMessage, normalizeRoleForGrouping } from "./message-normalizer.ts";
2+
import { isToolResultMessage, normalizeRoleForGrouping } from "./role-normalizer.ts";
33
import { getOrCreateSessionCacheValue } from "./session-cache.ts";
44
import { extractToolCards } from "./tool-cards.ts";
55

0 commit comments

Comments
 (0)