/** * OpenCode Agent Content Component * * Renders chat_agent messages in OpenCode style using OpenCodeSessionTurn. * This component handles both: * 1. Historical messages (parsing ReAct format from context string) * 2. Streaming messages (receiving real-time updates via props) */ import { ChatContext } from '@/app/chat-context'; import { parseReActText } from '@/hooks/use-react-agent'; import OpenCodeSessionTurn, { MessagePart } from '@/new-components/chat/content/OpenCodeSessionTurn'; import { IChatDialogueMessageSchema } from '@/types/chat'; import classNames from 'classnames'; import { memo, useContext, useMemo } from 'react'; interface Props { content: IChatDialogueMessageSchema; /** Optional streaming parts (for real-time updates) */ streamingParts?: MessagePart[]; /** Optional streaming final content */ streamingFinalContent?: string; /** Is currently working (streaming in progress) */ isWorking?: boolean; /** Start time for duration tracking */ startTime?: number; /** End time for duration tracking */ endTime?: number; /** Current status text */ currentStatus?: string; /** Additional className */ className?: string; } /** * Parse ReAct format text from message context * This handles the historical messages stored in the database */ function parseContextToMessageParts(context: string): { parts: MessagePart[]; finalContent: string } { if (!context || typeof context !== 'string') { return { parts: [], finalContent: '' }; } // Check if this looks like ReAct format const hasReActFormat = context.includes('Thought:') || context.includes('Action:') || context.includes('Action Input:') || context.includes('Observation:'); if (!hasReActFormat) { // Not ReAct format, return as plain text return { parts: [], finalContent: context }; } // Use the parseReActText function from the hook return parseReActText(context); } /** * Extract user message and assistant response from message pair */ function extractMessages( content: IChatDialogueMessageSchema, allMessages?: IChatDialogueMessageSchema[], ): { userMessage: string; assistantMessage: string; parts: MessagePart[] } { const isView = content.role === 'view'; const context = typeof content.context === 'string' ? content.context : JSON.stringify(content.context); if (isView) { // This is an assistant message (view role) const { parts, finalContent } = parseContextToMessageParts(context); // Try to find the corresponding user message let userMessage = ''; if (allMessages && content.order !== undefined) { const humanMsg = allMessages.find(m => m.role === 'human' && m.order === content.order); if (humanMsg) { userMessage = typeof humanMsg.context === 'string' ? humanMsg.context : JSON.stringify(humanMsg.context); } } return { userMessage, assistantMessage: finalContent || context, parts, }; } else { // This is a user message (human role) return { userMessage: context, assistantMessage: '', parts: [], }; } } /** * OpenCode Agent Content Component * * Renders agent messages with OpenCode-style tool execution visualization. */ function OpenCodeAgentContent({ content, streamingParts, streamingFinalContent, isWorking = false, startTime, endTime, currentStatus: _currentStatus, className, }: Props) { const { model } = useContext(ChatContext); const isView = content.role === 'view'; // Parse the content to get message parts const { userMessage, assistantMessage, parts } = useMemo(() => { return extractMessages(content); }, [content]); // Use streaming parts if provided, otherwise use parsed parts const displayParts = streamingParts || parts; const displayFinalContent = streamingFinalContent ?? assistantMessage; // Only render view messages with OpenCodeSessionTurn // Human messages will be rendered as part of the next view message if (!isView) { // For human messages, we just show the message simply // The full OpenCode turn will be rendered when the view message comes return (