Skip to content

Docs

Components

AgentMessage

A whole assistant message: reasoning, streaming markdown, tool calls, approvals and sources.

Example

Interactive, and the same on the components page. Try the keyboard below on it.

Installation

npm i @dgesteves/agent-ui-kit ai
import { AgentMessage } from '@dgesteves/agent-ui-kit';
// Once per app (with Tailwind v4: @import '@dgesteves/agent-ui-kit/tailwind.css'; in your CSS)
import '@dgesteves/agent-ui-kit/styles.css';

The styles are once per app; Getting started has the Tailwind v4 and plain CSS options.

Usage

const { messages, status, addToolApprovalResponse } = useChat();
const last = messages.findLast((m) => m.role === 'assistant');

{last && (
  <AgentMessage
    message={last}
    streaming={status === 'streaming'}
    tools={{ run_command: { label: 'Run command', risk: 'high' } }}
    onToolApproval={addToolApprovalResponse}
  />
)}

API reference

Generated from the types the package ships, so it matches the version you install.

AgentMessage

Renders an assistant UIMessage part by part: streaming markdown, collapsible reasoning, consecutive tool calls grouped into a timeline (with inline approval cards), files, and the message's sources with linked inline citations.

Props

messagerequired
Pick<UIMessage, 'id' | 'role' | 'parts'>
streaming
booleanDefault false

The message is still being generated. Text parts with an explicit state take precedence.

active
booleanDefault true

Whether the run can still make progress, including while it waits on the user. Pass false once it has ended (stopped, failed, or an older message): tool calls that never settled read "Stopped" instead of running forever. See ToolCallTimeline's active.

tools
Record<string, ToolMeta>
renderTool
(part: ToolPart) => ReactNode | undefined

Render a tool part yourself. Return undefined to use the default timeline, or null to render nothing. Custom-rendered parts split the timeline.

onToolApproval
(response: ToolApprovalResponse) => void | PromiseLike<void>

Enables inline approval cards. Pass useChat().addToolApprovalResponse.

approvalProps
Partial<Omit<ApprovalCardProps, 'toolName' | 'status' | 'onApprove' | 'onDeny'>>

Props forwarded to every approval card, e.g. { autoFocus: true }.

renderData
(part: DataPart) => ReactNode

Render data-* parts. They are skipped when omitted.

showSources
booleanDefault true
sourcesVariant
'chips' | 'cards'Default 'chips'
allowedImageHosts
readonly string[]

Where images in text, reasoning and image file parts may load from: host names, 'self' for relative URLs, or '*' for every image. Default: none, so other images render as links. File parts with data: and blob: URLs always preview. See MarkdownProps.allowedImageHosts.

timings
ToolTimings

Externally measured tool timings. Measured client-side when omitted.

Other props go to the root <article>: className (merged with tailwind-merge), id, aria-*, data-* and event handlers.

ToolMeta

Per-tool presentation. Every field is optional.

label
string

Display label. Defaults to the humanized tool name.

icon
ReactNode
summary
(input: unknown, part: ToolPart) => ReactNode

One-line summary shown next to the label. Defaults to the most descriptive input field. Not called before any input has arrived; while the input streams it can be partial.

renderOutput
(output: unknown, part: ToolPart) => ReactNode

Replace the default JSON output view.

risk
RiskLevel | ((input: unknown) => RiskLevel)

Risk shown on approval requests for this tool.

ToolApprovalResponse

idrequired
string
approvedrequired
boolean
reason
string

Accessibility

Keyboard
KeysAction
↑↓Move between tool calls in a timeline
YNApprove or deny, with focus in an approval card
  • The message is an <article> with aria-busy while it streams, so screen readers wait for the text to settle.
  • Each part keeps its own behavior: the tool timeline, approval cards, reasoning and sources below all apply.
  • Images in model output don't load unless allowedImageHosts allows them; a blocked image is a link with its alt text, read as "Image:".
  • Citation markers such as [2] become links named "Source 2" to the matching source.

Theming

Every color, radius and font is a CSS variable, so the overrides in Theming apply, on :root or scoped to any element. These hooks are read from the component's markup.

Slots (data-slot)
agent-message
State attributes
data-roleon agent-messagethe message’s role: assistant, user or system
Tokens it uses
--aui-border--aui-fg--aui-fg-subtle--aui-font-sans--aui-ring--aui-surface--aui-surface-2
app/globals.css
/* Only this component, and only inside .settings-panel */
.settings-panel [data-slot='agent-message'] {
  --aui-radius: 4px;
}