diff --git a/packages/ui/src/components/chat/ChatMessage.tsx b/packages/ui/src/components/chat/ChatMessage.tsx index ddd7b255..07391ad7 100644 --- a/packages/ui/src/components/chat/ChatMessage.tsx +++ b/packages/ui/src/components/chat/ChatMessage.tsx @@ -526,6 +526,26 @@ const ChatMessage: React.FC = ({ } const assistantSummaryForCopy = assistantSummaryRef.current; + const assistantErrorText = React.useMemo(() => { + if (isUser) { + return undefined; + } + const errorInfo = (message.info as { error?: unknown } | undefined)?.error as + | { data?: { message?: unknown }; message?: unknown; name?: unknown } + | undefined; + if (!errorInfo) { + return undefined; + } + const dataMessage = typeof errorInfo.data?.message === 'string' ? errorInfo.data.message : undefined; + const errorMessage = typeof errorInfo.message === 'string' ? errorInfo.message : undefined; + const errorName = typeof errorInfo.name === 'string' ? errorInfo.name : undefined; + const detail = dataMessage || errorMessage || errorName; + if (!detail) { + return undefined; + } + return `Opencode failed to send message with error:\n\`${detail}\``; + }, [isUser, message.info]); + const messageTextContent = React.useMemo(() => { if (isUser) { const textParts = displayParts @@ -540,12 +560,16 @@ const ChatMessage: React.FC = ({ return combined.replace(/\n\s*\n+/g, '\n'); } + if (assistantErrorText && assistantErrorText.trim().length > 0) { + return assistantErrorText; + } + if (assistantSummaryForCopy && assistantSummaryForCopy.trim().length > 0) { return assistantSummaryForCopy; } return flattenAssistantTextParts(displayParts); - }, [assistantSummaryForCopy, displayParts, isUser]); + }, [assistantErrorText, assistantSummaryForCopy, displayParts, isUser]); const hasTextContent = messageTextContent.length > 0; @@ -767,6 +791,7 @@ const ChatMessage: React.FC = ({ agentMention={agentMention} onRevert={handleRevert} isFirstMessage={isFirstMessage} + errorMessage={assistantErrorText} /> @@ -808,6 +833,7 @@ const ChatMessage: React.FC = ({ showReasoningTraces={showReasoningTraces} agentMention={agentMention} turnGroupingContext={turnGroupingContext} + errorMessage={assistantErrorText} /> diff --git a/packages/ui/src/components/chat/message/MessageBody.tsx b/packages/ui/src/components/chat/message/MessageBody.tsx index d45db717..486eb720 100644 --- a/packages/ui/src/components/chat/message/MessageBody.tsx +++ b/packages/ui/src/components/chat/message/MessageBody.tsx @@ -130,6 +130,7 @@ interface MessageBodyProps { turnGroupingContext?: TurnGroupingContext; onRevert?: () => void; isFirstMessage?: boolean; + errorMessage?: string; } const UserMessageBody: React.FC<{ @@ -318,6 +319,7 @@ const AssistantMessageBody: React.FC> = ({ onAuxiliaryContentComplete, showReasoningTraces = false, turnGroupingContext, + errorMessage, }) => { void _streamPhase; @@ -1051,7 +1053,9 @@ const AssistantMessageBody: React.FC> = ({ summaryBody && summaryBody.trim().length > 0; - const shouldShowFooter = hasTextContent && assistantTextParts.length > 0 && hasStopFinish && isLastAssistantInTurn; + const showErrorMessage = Boolean(errorMessage); + + const shouldShowFooter = isLastAssistantInTurn && hasTextContent && (hasStopFinish || Boolean(errorMessage)); const [isSummaryHovered, setIsSummaryHovered] = React.useState(false); const footerButtons = ( @@ -1131,6 +1135,13 @@ const AssistantMessageBody: React.FC> = ({ className="leading-normal overflow-hidden text-foreground/90 [&_p:last-child]:mb-0 [&_ul:last-child]:mb-0 [&_ol:last-child]:mb-0" > {renderedParts} + {showErrorMessage && ( + +
+ +
+
+ )} {showSummaryBody && (