'use client'; import { memo, type ReactElement } from 'react'; import type { Message } from '@/lib/types/http'; import type { ToolCallMetadata } from '@/components/task/chat/types'; import { ChatMessage } from '@/components/task/chat/messages/chat-message'; import { PermissionRequestMessage } from '@/components/task/chat/messages/permission-request-message'; import { StatusMessage } from '@/components/task/chat/messages/status-message'; import { ToolCallMessage } from '@/components/task/chat/messages/tool-call-message'; import { ToolEditMessage } from '@/components/task/chat/messages/tool-edit-message'; import { ToolReadMessage } from '@/components/task/chat/messages/tool-read-message'; import { ToolSearchMessage } from '@/components/task/chat/messages/tool-search-message'; import { ToolExecuteMessage } from '@/components/task/chat/messages/tool-execute-message'; import { ThinkingMessage } from '@/components/task/chat/messages/thinking-message'; import { TodoMessage } from '@/components/task/chat/messages/todo-message'; import { ScriptExecutionMessage } from '@/components/task/chat/messages/script-execution-message'; import { ClarificationRequestMessage } from '@/components/task/chat/messages/clarification-request-message'; import { ToolSubagentMessage } from '@/components/task/chat/messages/tool-subagent-message'; type AdapterContext = { isTaskDescription: boolean; taskId?: string; permissionsByToolCallId?: Map; childrenByParentToolCallId?: Map; worktreePath?: string; sessionId?: string; onOpenFile?: (path: string) => void; allMessages?: Message[]; onScrollToMessage?: (messageId: string) => void; }; type MessageAdapter = { matches: (comment: Message, ctx: AdapterContext) => boolean; render: (comment: Message, ctx: AdapterContext) => ReactElement; }; const adapters: MessageAdapter[] = [ { matches: (comment) => comment.type === 'thinking', render: (comment) => , }, { matches: (comment) => comment.type === 'todo', render: (comment) => , }, { matches: (comment) => comment.type === 'tool_edit', render: (comment, ctx) => , }, { matches: (comment) => comment.type === 'tool_read', render: (comment, ctx) => , }, { matches: (comment) => comment.type === 'tool_search', render: (comment, ctx) => , }, { matches: (comment) => comment.type === 'tool_execute', render: (comment, ctx) => , }, { // Subagent Task tool calls with nested children matches: (comment, ctx) => { if (comment.type !== 'tool_call') return false; const metadata = comment.metadata as ToolCallMetadata | undefined; const isSubagent = metadata?.normalized?.kind === 'subagent_task'; const toolCallId = metadata?.tool_call_id; const hasChildren = toolCallId ? (ctx.childrenByParentToolCallId?.has(toolCallId) ?? false) : false; return isSubagent || hasChildren; }, render: (comment, ctx) => { const toolCallId = (comment.metadata as ToolCallMetadata | undefined)?.tool_call_id; const childMessages = toolCallId ? ctx.childrenByParentToolCallId?.get(toolCallId) ?? [] : []; // Create a render function for child messages const renderChild = (child: Message) => { // Recursively use MessageRenderer for children (without subagent nesting) const childCtx = { ...ctx, childrenByParentToolCallId: undefined }; const adapter = adapters.find((entry) => entry.matches(child, childCtx)) ?? adapters[adapters.length - 1]; return adapter.render(child, childCtx); }; return ( ); }, }, { matches: (comment) => comment.type === 'tool_call', render: (comment, ctx) => { const toolCallId = (comment.metadata as { tool_call_id?: string } | undefined)?.tool_call_id; const permissionMessage = toolCallId ? ctx.permissionsByToolCallId?.get(toolCallId) : undefined; return ; }, }, { matches: (comment) => comment.type === 'error' || comment.type === 'status' || comment.type === 'progress', render: (comment) => , }, { // Standalone permission requests (no matching tool call) matches: (comment) => comment.type === 'permission_request', render: (comment) => , }, { matches: (comment) => comment.type === 'clarification_request', render: (comment) => , }, { matches: (comment) => comment.type === 'script_execution', render: (comment) => , }, { matches: () => true, render: (comment, ctx) => { if (ctx.isTaskDescription) { return ( ); } if (comment.author_type === 'user') { return ( ); } return ( ); }, }, ]; type MessageRendererProps = { comment: Message; isTaskDescription: boolean; taskId?: string; permissionsByToolCallId?: Map; childrenByParentToolCallId?: Map; worktreePath?: string; sessionId?: string; onOpenFile?: (path: string) => void; allMessages?: Message[]; onScrollToMessage?: (messageId: string) => void; }; export const MessageRenderer = memo(function MessageRenderer({ comment, isTaskDescription, taskId, permissionsByToolCallId, childrenByParentToolCallId, worktreePath, sessionId, onOpenFile, allMessages, onScrollToMessage, }: MessageRendererProps) { const ctx = { isTaskDescription, taskId, permissionsByToolCallId, childrenByParentToolCallId, worktreePath, sessionId, onOpenFile, allMessages, onScrollToMessage }; const adapter = adapters.find((entry) => entry.matches(comment, ctx)) ?? adapters[adapters.length - 1]; return adapter.render(comment, ctx); });