Show task notifications as synthetic tool calls in chat (#79)
* fix(server): render claude task notifications as synthetic tool calls * refactor(claude): extract task-notification tool-call mapper * refactor: drive tool icons from plain_text detail metadata * refactor(claude): make task-notification parsing schema-first
This commit is contained in:
@@ -7,6 +7,7 @@ const iconMocks = vi.hoisted(() => ({
|
||||
MicVocal: () => null,
|
||||
Pencil: () => null,
|
||||
Search: () => null,
|
||||
Sparkles: () => null,
|
||||
SquareTerminal: () => null,
|
||||
Wrench: () => null,
|
||||
}));
|
||||
@@ -48,4 +49,24 @@ describe("tool-call-icon", () => {
|
||||
|
||||
expect(icon).toBe(iconMocks.Brain);
|
||||
});
|
||||
|
||||
it("uses custom icon from plain_text detail", () => {
|
||||
const icon = resolveToolCallIcon("custom_tool", {
|
||||
type: "plain_text",
|
||||
label: "Background task completed",
|
||||
icon: "sparkles",
|
||||
text: "notification payload",
|
||||
});
|
||||
|
||||
expect(icon).toBe(iconMocks.Sparkles);
|
||||
});
|
||||
|
||||
it("does not special-case skill tool names", () => {
|
||||
const icon = resolveToolCallIcon("skill", {
|
||||
type: "plain_text",
|
||||
label: "Skill output",
|
||||
});
|
||||
|
||||
expect(icon).toBe(iconMocks.Wrench);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
SquareTerminal,
|
||||
Wrench,
|
||||
} from "lucide-react-native";
|
||||
import type { ToolCallDetail } from "@server/server/agent/agent-sdk-types";
|
||||
import type { ToolCallDetail, ToolCallIconName } from "@server/server/agent/agent-sdk-types";
|
||||
|
||||
export type ToolCallIconComponent = ComponentType<{ size?: number; color?: string }>;
|
||||
|
||||
@@ -26,9 +26,25 @@ const TOOL_DETAIL_ICONS: Record<ToolCallDetail["type"], ToolCallIconComponent> =
|
||||
unknown: Wrench,
|
||||
};
|
||||
|
||||
const TOOL_ICON_BY_NAME: Record<ToolCallIconName, ToolCallIconComponent> = {
|
||||
wrench: Wrench,
|
||||
square_terminal: SquareTerminal,
|
||||
eye: Eye,
|
||||
pencil: Pencil,
|
||||
search: Search,
|
||||
bot: Bot,
|
||||
sparkles: Sparkles,
|
||||
brain: Brain,
|
||||
mic_vocal: MicVocal,
|
||||
};
|
||||
|
||||
export function resolveToolCallIcon(toolName: string, detail?: ToolCallDetail): ToolCallIconComponent {
|
||||
const lowerName = toolName.trim().toLowerCase();
|
||||
|
||||
if (detail?.type === "plain_text" && detail.icon) {
|
||||
return TOOL_ICON_BY_NAME[detail.icon];
|
||||
}
|
||||
|
||||
// Thoughts are rendered through ToolCall with unknown detail payloads.
|
||||
if (lowerName === "thinking" && (!detail || detail.type === "unknown")) {
|
||||
return Brain;
|
||||
@@ -39,9 +55,6 @@ export function resolveToolCallIcon(toolName: string, detail?: ToolCallDetail):
|
||||
if (lowerName === "task") {
|
||||
return Bot;
|
||||
}
|
||||
if (lowerName === "skill") {
|
||||
return Sparkles;
|
||||
}
|
||||
|
||||
if (detail) {
|
||||
return TOOL_DETAIL_ICONS[detail.type];
|
||||
|
||||
Reference in New Issue
Block a user