fix(chat): display error messages for failed assistant messages

This commit is contained in:
Bohdan Triapitsyn
2026-01-01 14:25:46 +02:00
parent 3d1e8fa98d
commit 2c1c960d5f
2 changed files with 39 additions and 2 deletions
@@ -526,6 +526,26 @@ const ChatMessage: React.FC<ChatMessageProps> = ({
}
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<ChatMessageProps> = ({
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<ChatMessageProps> = ({
agentMention={agentMention}
onRevert={handleRevert}
isFirstMessage={isFirstMessage}
errorMessage={assistantErrorText}
/>
</div>
</div>
@@ -808,6 +833,7 @@ const ChatMessage: React.FC<ChatMessageProps> = ({
showReasoningTraces={showReasoningTraces}
agentMention={agentMention}
turnGroupingContext={turnGroupingContext}
errorMessage={assistantErrorText}
/>
</div>
@@ -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<Omit<MessageBodyProps, 'isUser'>> = ({
onAuxiliaryContentComplete,
showReasoningTraces = false,
turnGroupingContext,
errorMessage,
}) => {
void _streamPhase;
@@ -1051,7 +1053,9 @@ const AssistantMessageBody: React.FC<Omit<MessageBodyProps, 'isUser'>> = ({
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<Omit<MessageBodyProps, 'isUser'>> = ({
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 && (
<FadeInOnReveal key="assistant-error">
<div className="group/assistant-text relative break-words">
<SimpleMarkdownRenderer content={errorMessage ?? ''} />
</div>
</FadeInOnReveal>
)}
{showSummaryBody && (
<FadeInOnReveal key="summary-body">
<div