import type { ToolCallMessagePartProps } from '@assistant-ui/react' import type { ChatBlock } from '@/chat/types' import type { ToolCallBlock } from '@/chat/types' import { getEventPresentation } from '@/chat/presentation' import { CodeBlock } from '@/components/CodeBlock' import { MarkdownRenderer } from '@/components/MarkdownRenderer' import { LazyRainbowText } from '@/components/LazyRainbowText' import { MessageStatusIndicator } from '@/components/AssistantChat/messages/MessageStatusIndicator' import { ToolCard } from '@/components/ToolCard/ToolCard' import { useHappyChatContext } from '@/components/AssistantChat/context' import { CliOutputBlock } from '@/components/CliOutputBlock' function isObject(value: unknown): value is Record { return Boolean(value) && typeof value === 'object' } function safeStringify(value: unknown): string { if (typeof value === 'string') return value try { const stringified = JSON.stringify(value, null, 2) return typeof stringified === 'string' ? stringified : String(value) } catch { return String(value) } } function isToolCallBlock(value: unknown): value is ToolCallBlock { if (!isObject(value)) return false if (value.kind !== 'tool-call') return false if (typeof value.id !== 'string') return false if (value.localId !== null && typeof value.localId !== 'string') return false if (typeof value.createdAt !== 'number') return false if (!Array.isArray(value.children)) return false if (!isObject(value.tool)) return false if (typeof value.tool.name !== 'string') return false if (!('input' in value.tool)) return false if (value.tool.description !== null && typeof value.tool.description !== 'string') return false if (value.tool.state !== 'pending' && value.tool.state !== 'running' && value.tool.state !== 'completed' && value.tool.state !== 'error') return false return true } function HappyNestedBlockList(props: { blocks: ChatBlock[] }) { const ctx = useHappyChatContext() return (
{props.blocks.map((block) => { if (block.kind === 'user-text') { const userBubbleClass = 'w-fit max-w-[92%] ml-auto rounded-xl bg-[var(--app-secondary-bg)] px-3 py-2 text-[var(--app-fg)] shadow-sm' const status = block.status const canRetry = status === 'failed' && typeof block.localId === 'string' && Boolean(ctx.onRetryMessage) const onRetry = canRetry ? () => ctx.onRetryMessage!(block.localId!) : undefined return (
{status ? (
) : null}
) } if (block.kind === 'agent-text') { return (
) } if (block.kind === 'cli-output') { const alignClass = block.source === 'user' ? 'ml-auto w-full max-w-[92%]' : '' return (
) } if (block.kind === 'agent-event') { const presentation = getEventPresentation(block.event) return (
{presentation.icon ? : null} {presentation.text}
) } if (block.kind === 'tool-call') { const isTask = block.tool.name === 'Task' return (
{block.children.length > 0 ? ( isTask ? (
Task details ({block.children.length})
) : (
) ) : null}
) } return null })}
) } export function HappyToolMessage(props: ToolCallMessagePartProps) { const ctx = useHappyChatContext() const artifact = props.artifact if (!isToolCallBlock(artifact)) { const argsText = typeof props.argsText === 'string' ? props.argsText.trim() : '' const hasArgsText = argsText.length > 0 const hasResult = props.result !== undefined const resultText = hasResult ? safeStringify(props.result) : '' return (
Tool: {props.toolName}
{props.isError ? ( Error ) : null} {props.status.type === 'running' && !hasResult ? ( Running… ) : null}
{hasArgsText ? (
) : null} {hasResult ? (
) : null}
) } const block = artifact const isTask = block.tool.name === 'Task' return (
{block.children.length > 0 ? ( isTask ? (
Task details ({block.children.length})
) : (
) ) : null}
) }