Summary: components.AssistantMessage could render live tool progress before 0.13; after #916 it isn't mounted during the tool-only phase and receives toolCalls: []. There's no supported way to replace the turn-level timeline.
Question for maintainers: would you prefer restoring the messageGroup prop #916's description mentions, or adding a ToolCallTimeline slot to AgentInterfaceComponents? Happy to implement either with tests and docs once you confirm.
Describe the bug
Before @openuidev/react-ui 0.13, an AgentInterface components.AssistantMessage override represented the complete assistant turn. It could use useToolActivities() to render live tool progress as well as the final answer.
After PR #916, assistant/tool messages are grouped by the internal InterleavedTurn. That component now:
- Renders
ToolCallTimeline directly, with no component override.
- Does not mount the custom
AssistantMessage during the tool-only phase.
- Passes only the final answer to the custom component.
- Explicitly removes its tool calls:
const answerMessage = answer ? {...answer, toolCalls: []} : null;
As a result, an application cannot replace the built-in turn-level tool timeline while continuing to use AgentInterface.
This appears to be a regression in the documented customization contract. The current message-rendering documentation describes components as the escape hatch from built-in rendering and says custom components render every assistant and user message:
https://github.com/thesysdev/openui/blob/main/docs/content/docs/agent/customize/message-rendering.mdx
PR #916 also states:
New optional messageGroup prop on AssistantMessageComponent — existing custom components are unaffected
However, messageGroup is absent from the merged implementation, 0.13.10, and current main.
To Reproduce
-
Configure AgentInterface with an LLM adapter that produces a tool call, tool result, and final assistant answer.
-
Supply a custom assistant component:
import {
AgentInterface,
type AssistantMessage,
useThread,
useToolActivities,
} from "@openuidev/react-ui";
function CustomAssistantMessage({
message,
isStreaming,
}: {
message: AssistantMessage;
isStreaming: boolean;
}) {
const messages = useThread((state) => state.messages);
const activities = useToolActivities(message, messages);
return (
<div>
<div>Activities visible to override: {activities.length}</div>
<div>{message.content}</div>
</div>
);
}
<AgentInterface
llm={llm}
components={{AssistantMessage: CustomAssistantMessage}}
/>;
-
Start a turn that calls one or more tools.
-
Observe the UI while the tools are running and after the answer begins.
Actual behavior
- During the tool-only phase, only the built-in
ToolCallTimeline is rendered.
CustomAssistantMessage cannot render live tool progress because it is not mounted for that phase.
- When it is mounted for the final answer,
message.toolCalls has been replaced with an empty array, so useToolActivities(message, messages) returns no activities for the turn.
- There is no
AgentInterface prop or component slot that suppresses and replaces the built-in timeline.
This prevents applications from implementing a custom presentation such as a collapsed-by-default:
Show research steps · used 3 tools
panel that updates live without the built-in staggered timeline.
Expected behavior
There should be a supported way to replace the turn-level tool-call timeline without rebuilding the entire thread or chat shell. The default behavior should remain exactly as it is when no override is provided.
Suggested solution
Add an optional timeline component to AgentInterfaceComponents, for example:
export type ToolCallTimelineComponent = React.ComponentType<{
activities: ToolActivity[];
steps: TimelineStep[];
isLast: boolean;
awaitingResponse: boolean;
}>;
export interface AgentInterfaceComponents {
AssistantMessage?: AssistantMessageComponent;
UserMessage?: UserMessageComponent;
ToolCallTimeline?: ToolCallTimelineComponent;
}
InterleavedTurn could use the override when provided and otherwise preserve the existing implementation:
{turnActivities.length > 0 &&
(CustomToolCallTimeline ? (
<CustomToolCallTimeline
activities={turnActivities}
steps={steps}
isLast={turnLive}
awaitingResponse={turnLive && !answerStarted}
/>
) : (
<ToolCallTimeline
activities={turnActivities}
steps={steps}
isLast={turnLive}
forceDefault
awaitingResponse={turnLive && !answerStarted}
/>
))}
It would also be useful to expose the equivalent prop on AgentInterface.Messages, mirroring its existing assistantMessage and userMessage props.
The exact API naming is open for discussion. The required capability is for the replacement component to:
- Mount as soon as the first tool activity appears, before answer content.
- Receive live activities and the ordered timeline steps.
- Replace, rather than render alongside, the built-in timeline.
- Leave matched artifact/tool previews unchanged.
- Preserve all existing behavior when the override is omitted.
ToolActivity, TimelineStep, ToolCallTimeline, TimelineEntry, and useToolActivities are already public, so this should require little new public surface.
Alternatives considered
-
Continue using components.AssistantMessage
This cannot render during the tool-only phase, and the message it eventually receives has toolCalls removed.
-
Use artifactRenderers
These customize matched tool previews, not the turn-level timeline or its wrapper. The built-in timeline remains present.
-
Hide the built-in timeline with CSS
It can be hidden, but there is still no custom component mounted during the tool-only phase to replace it.
-
Build a custom thread with AgentInterface.Route
Technically possible, but Route is intended for alternate pages. The application would need to own message grouping, turn rendering, loading and error behavior, routing details, and future compatibility.
-
Build a complete shell using react-headless
Provides full control but requires replacing substantially more of AgentInterface.
-
Patch the installed package
A temporary workaround, but it must be maintained across upgrades and does not provide a supported public API.
Environment
- Worked as expected:
@openuidev/react-ui@0.12.1
- Regression observed after:
@openuidev/react-ui@0.13.x
- Confirmed absent from:
@openuidev/react-ui@0.13.10
- React: 18.3.1
- Browser/OS: not browser-specific
Additional context
The original customization renders one collapsed research-steps panel per assistant turn. It updates its tool count during streaming and expands to show the existing public TimelineEntry components.
I would be happy to open a focused PR with the component slot, documentation, and a regression test after a maintainer confirms the preferred API shape.
Summary:
components.AssistantMessagecould render live tool progress before 0.13; after #916 it isn't mounted during the tool-only phase and receivestoolCalls: []. There's no supported way to replace the turn-level timeline.Question for maintainers: would you prefer restoring the
messageGroupprop #916's description mentions, or adding aToolCallTimelineslot toAgentInterfaceComponents? Happy to implement either with tests and docs once you confirm.Describe the bug
Before
@openuidev/react-ui0.13, anAgentInterfacecomponents.AssistantMessageoverride represented the complete assistant turn. It could useuseToolActivities()to render live tool progress as well as the final answer.After PR #916, assistant/tool messages are grouped by the internal
InterleavedTurn. That component now:ToolCallTimelinedirectly, with no component override.AssistantMessageduring the tool-only phase.As a result, an application cannot replace the built-in turn-level tool timeline while continuing to use
AgentInterface.This appears to be a regression in the documented customization contract. The current message-rendering documentation describes
componentsas the escape hatch from built-in rendering and says custom components render every assistant and user message:https://github.com/thesysdev/openui/blob/main/docs/content/docs/agent/customize/message-rendering.mdx
PR #916 also states:
However,
messageGroupis absent from the merged implementation, 0.13.10, and currentmain.To Reproduce
Configure
AgentInterfacewith an LLM adapter that produces a tool call, tool result, and final assistant answer.Supply a custom assistant component:
Start a turn that calls one or more tools.
Observe the UI while the tools are running and after the answer begins.
Actual behavior
ToolCallTimelineis rendered.CustomAssistantMessagecannot render live tool progress because it is not mounted for that phase.message.toolCallshas been replaced with an empty array, souseToolActivities(message, messages)returns no activities for the turn.AgentInterfaceprop or component slot that suppresses and replaces the built-in timeline.This prevents applications from implementing a custom presentation such as a collapsed-by-default:
Show research steps · used 3 tools
panel that updates live without the built-in staggered timeline.
Expected behavior
There should be a supported way to replace the turn-level tool-call timeline without rebuilding the entire thread or chat shell. The default behavior should remain exactly as it is when no override is provided.
Suggested solution
Add an optional timeline component to
AgentInterfaceComponents, for example:InterleavedTurncould use the override when provided and otherwise preserve the existing implementation:It would also be useful to expose the equivalent prop on
AgentInterface.Messages, mirroring its existingassistantMessageanduserMessageprops.The exact API naming is open for discussion. The required capability is for the replacement component to:
ToolActivity,TimelineStep,ToolCallTimeline,TimelineEntry, anduseToolActivitiesare already public, so this should require little new public surface.Alternatives considered
Continue using
components.AssistantMessageThis cannot render during the tool-only phase, and the message it eventually receives has
toolCallsremoved.Use
artifactRenderersThese customize matched tool previews, not the turn-level timeline or its wrapper. The built-in timeline remains present.
Hide the built-in timeline with CSS
It can be hidden, but there is still no custom component mounted during the tool-only phase to replace it.
Build a custom thread with
AgentInterface.RouteTechnically possible, but
Routeis intended for alternate pages. The application would need to own message grouping, turn rendering, loading and error behavior, routing details, and future compatibility.Build a complete shell using
react-headlessProvides full control but requires replacing substantially more of
AgentInterface.Patch the installed package
A temporary workaround, but it must be maintained across upgrades and does not provide a supported public API.
Environment
@openuidev/react-ui@0.12.1@openuidev/react-ui@0.13.x@openuidev/react-ui@0.13.10Additional context
The original customization renders one collapsed research-steps panel per assistant turn. It updates its tool count during streaming and expands to show the existing public
TimelineEntrycomponents.I would be happy to open a focused PR with the component slot, documentation, and a regression test after a maintainer confirms the preferred API shape.