Getting Started
Build a custom chat experience on Limio's conversational agent.
The agent client SDK lets a custom component talk to Limio's conversational agent. You import one hook, useChat, and render the conversation however you like. The hook handles the session, the conversation, optimistic messages, and error recovery; you handle the markup and styling.
The agent client SDK (@limio/sdk/ai) is available from Release 117 onwards. If your environment is on an earlier release, contact your Limio admin about upgrading.
What can you build?
Any chat or agent surface that runs on a Limio page:
Inline panel
A chat card embedded in the page, like the Chat Panel component.
Exit-intent popup
A prompt that opens when the visitor is about to leave.
Full-page assistant
A dedicated support or shopping page.
Sidebar or drawer
A persistent assistant alongside your content.
They all use the same hook. What differs is your UI.
Import the SDK
The client SDK ships as a subpath of @limio/sdk, which the platform already provides to your components at runtime:
import { useChat, MessageContent, contentToText } from "@limio/sdk/ai"Import from @limio/sdk/ai, not @limio/sdk. The agent SDK is kept off the main entry so pages without chat never load it. Importing the subpath bundles it into your component; non-chat components stay lean.
Your first chat surface
A minimal component: a greeting, the message list, and an input wired to send.
What's happening here:
useChat({ greeting })bootstraps a session on mount and seeds the greeting as the first assistant message. Omitgreetingto use the agent's server-configured greeting.messagesis the live conversation. Each message has anid, arole("user"or"assistant"), and structuredcontent.send(text)adds the visitor's message immediately, shows a pending assistant bubble, creates the conversation on the first call, then replaces the pending bubble with the agent's reply. On failure it rolls the bubble back and keeps the visitor's message.The pending bubble shows
…only while its content is empty, rather than for as long aspendingis set, because a streamed reply fills that bubble as the text arrives. See Streaming replies.statusis"idle","ready","sending", or"error". Use"sending"to disable the input and prevent double-sends.
Rendering messages
A message's content is an array of blocks, not a string. How you render it depends on the surface.
Formatted replies. MessageContent renders each block correctly: text/markdown (agent replies and the greeting) through the markdown renderer, and text/plain (the visitor's words) literally. This is the same formatting the built-in widget uses.
markdownClassName is applied to the wrapper around each rendered markdown block, so you can style the generated HTML (lists, bold, links, code, tables).
Plain text only. If your surface does not need formatting, flatten the content to a string with contentToText:
Render the visitor's own messages with contentToText (their input is never markdown), and the agent's messages with MessageContent (so formatting shows).
Streaming replies
An admin can turn on Stream replies for an agent (see Configuring an Agent) so the reply appears as it is written instead of arriving whole. Your surface needs no code change to support it: useChat reads the setting from the session and picks the transport itself.
What it does need is to render the pending bubble on its content rather than on the flag. A streamed reply grows in that bubble's content while pending stays true, so a surface that shows a typing indicator for as long as pending is set hides the very text it is streaming, and the reply lands whole at the end as though nothing had streamed. The bubble starts empty, so the test is its content:
The bubble settles into an ordinary message when the reply finishes, holding exactly what it would have held without streaming, checkout links and all. Written this way a surface renders correctly whether or not the agent streams, and keeps up when an admin changes the setting.
Smoothing (optional). Models emit replies in a few large chunks rather than character by character, so text rendered straight from content arrives in jumps. useSmoothedText reveals a growing string at a steady rate, which reads as typing:
It animates growth only, so settled messages render whole and are never re-typed, and it does nothing under prefers-reduced-motion.
Interactive components (optional)
The agent can include an interactive component in a reply, such as a file upload. These arrive as application/vnd.limio.component+json blocks. To render them, pass a registry to MessageContent:
Register only the components your surface supports. Component blocks with no matching renderer are skipped, so a text-only surface can omit the registry entirely.
Starting over
reset() clears the messages, re-seeds the greeting, and drops the conversation while keeping the session. Wire it to a "new chat" control:
Deferring the session
By default useChat bootstraps the session on mount. For a surface that stays hidden until the visitor opens it (an exit-intent popup, a collapsed drawer), defer the session with autoStart:
Pass false while hidden and flip it to true when shown, or call start() yourself.
A working example
The Chat Panel component in the Page Builder is built entirely on @limio/sdk/ai: useChat plus its own inline card UI, with no vendored transport. It is the reference for a custom surface, with a greeting, markdown-rendered replies, a typing indicator, and configurable colours. Add it from the component picker to see the SDK in action.
Reference
For the full API (every useChat option and return value, the transport functions, the renderer props, and the registry), see the SDK Reference.
Last updated
Was this helpful?

