> For the complete documentation index, see [llms.txt](https://docs.limio.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.limio.com/ai/limio-agents/getting-started.md).

# Getting Started

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.

{% hint style="info" %}
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.
{% endhint %}

***

## What can you build?

Any chat or agent surface that runs on a Limio page:

| Surface                 | Notes                                                            |
| ----------------------- | ---------------------------------------------------------------- |
| **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:

```ts
import { useChat, MessageContent, contentToText } from "@limio/sdk/ai"
```

{% hint style="info" %}
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.
{% endhint %}

***

## Your first chat surface

A minimal component: a greeting, the message list, and an input wired to `send`.

```tsx
import React, { useState } from "react"
import { useChat, MessageContent, contentToText } from "@limio/sdk/ai"

const ChatSurface = () => {
  const { messages, send, status } = useChat({
    greeting: "Hi! How can I help?"
  })
  const [input, setInput] = useState("")
  const sending = status === "sending"

  const onSubmit = (e) => {
    e.preventDefault()
    const text = input.trim()
    if (!text || sending) return
    setInput("")
    void send(text)
  }

  return (
    <div className="chat">
      <div className="chat-messages">
        {messages.map((m) =>
          m.role === "user" ? (
            <p key={m.id} className="chat-user">
              {contentToText(m.content)}
            </p>
          ) : (
            <div key={m.id} className="chat-agent">
              {m.pending && m.content.length === 0 ? <span>…</span> : <MessageContent content={m.content} markdownClassName="chat-md" />}
            </div>
          )
        )}
      </div>

      <form onSubmit={onSubmit}>
        <input value={input} onChange={(e) => setInput(e.target.value)} placeholder="Type a message" disabled={sending} />
        <button type="submit" disabled={sending || !input.trim()}>
          Send
        </button>
      </form>
    </div>
  )
}

export default ChatSurface
```

**What's happening here:**

1. `useChat({ greeting })` bootstraps a session on mount and seeds the greeting as the first assistant message. Omit `greeting` to use the agent's server-configured greeting.
2. `messages` is the live conversation. Each message has an `id`, a `role` (`"user"` or `"assistant"`), and structured `content`.
3. `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.
4. The pending bubble shows `…` only while its content is empty, rather than for as long as `pending` is set, because a streamed reply fills that bubble as the text arrives. While the agent runs tools, the bubble also carries a short `status` line ("Finding the right offers...") worth rendering in place of the dots. See [Streaming replies](#streaming-replies).
5. `status` is `"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.

```tsx
<MessageContent content={message.content} markdownClassName="chat-md" />
```

`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`:

```tsx
<p>{contentToText(message.content)}</p>
```

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](/ai/limio-agents/configuring-an-agent.md#stream-replies)) 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:

```tsx
{m.pending && m.content.length === 0 ? <span>…</span> : <MessageContent content={m.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:

```tsx
import { useSmoothedText, contentToText } from "@limio/sdk/ai"

const AgentText = ({ message }) => {
  const shown = useSmoothedText(contentToText(message.content))
  return <p>{shown}</p>
}
```

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`:

```tsx
import { createMessageComponentRegistry, MessageContent } from "@limio/sdk/ai"

const registry = createMessageComponentRegistry({
  file_upload: (props, ctx) => (
    <MyUploader {...props} conversationId={ctx.conversationId} />
  )
})

const { messages, conversation } = useChat()

<MessageContent
  content={message.content}
  registry={registry}
  conversationId={conversation?.id ?? null}
/>
```

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 and drops the conversation while keeping the session. A `greeting` you passed in options is re-seeded, as here; with the server-configured greeting the list resets to empty. Wire it to a "new chat" control:

```tsx
const { messages, send, status, reset } = useChat({ greeting: "Hi!" })

<button onClick={reset}>New chat</button>
```

***

## 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`:

```tsx
const { messages, send } = useChat({ greeting: "Hi!", autoStart: open })
```

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 on `@limio/sdk/ai`: `useChat` plus the widget's conversation primitives inside its own card frame, 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](/ai/limio-agents/sdk-reference.md).


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.limio.com/ai/limio-agents/getting-started.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
