Skip to main content

Why Use These?

Building a chat list by hand means handling placeholder bubbles, tool cards, empty slots between tool calls, paging through history, and error recovery. The SDK ships two classes that do all of that for you: Both update on the main thread and work with @Observable, so SwiftUI redraws when they change. Keep them in @State, in a view model, or in the environment, whichever your app already does.

ConversationState

@MainActor @Observable final class ConversationState

init

ChatbaseClient
required
The client to send through. Register your tools on it first.
String?
Reopen an existing conversation. Leave it out for a new one.

A complete chat screen

Notice what you did not have to write: no placeholder handling, no joining text chunks together, no tool loop tracking.

Showing a message

What you can read

You cannot set these directly. Use the methods below.

sendMessage

Adds the user’s message, streams the reply into a new bubble, runs the tools, and tidies up at the end. It never throws. Errors go into error, and the failed bubble is marked with isError.
Empty text is ignored, and so is a call made while isSending is true, so you do not have to guard the button yourself (though disabling it is nicer). The conversation ID is taken from the first reply and reused after that.

retry

Asks the agent to answer again. That message and everything after it are removed from messages, then the new reply streams in.
Pass the server’s ID from UiMessage.messageId, not UiMessage.id. The call is ignored if a message is already being sent, if there is no conversation yet, or if that ID is not in the list.

loadHistory

Replaces messages with the newest page of an existing conversation and switches to it. Use it when opening a conversation from a list:

loadMoreHistory

Loads the next page of older messages and adds them to the top, skipping any you already have. Does nothing when hasMoreHistory is false or a load is already running.

clear

Resets everything: messages, conversation ID, history, and error. Call it when the user signs out, together with client.logout(), or when starting a new chat:

clearError and setConversationId

clearError() dismisses the last error. setConversationId(_:) points the state at a conversation without loading its messages, which is useful if you already have them from somewhere else.

UiMessage

struct ConversationState.UiMessage: Identifiable, Sendable One row on screen. A single message from the server can become several UiMessage values, one per part, so text and tool cards appear in the order the agent made them.
String
required
A stable ID for ForEach. Made locally for new rows, and from the server’s message for older ones.
String?
The server’s message ID, filled in once the reply finishes. Pass this to retry(messageId:).
Kind
required
.text(String) for a bubble, .toolCall(ToolCallCard) for a tool card.
MessageSender
required
.user or .agent. Tool cards are always .agent.
Date
required
When the row was made, or when the message was sent.
Bool
required
true while text is still arriving in this bubble. Use it to show a typing dot or a cursor.
Bool
required
true when the reply failed. Show a retry button.
MessageFeedback?
.positive, .negative, or nil. Set for messages loaded from history.
id and messageId are different on purpose. id exists as soon as a row appears, so SwiftUI can animate it. messageId only exists once the server has given the message an ID. Use id for ForEach and scrollTo, and messageId for retry.

ToolCallCard

struct ConversationState.ToolCallCard: Sendable
A card appears as .executing as soon as the agent asks for the tool, then becomes .success or .failure when the result arrives.
The status follows the SDK’s usual rule: a result that is an object with an error key counts as .failure, and anything else counts as .success. This is the same rule your handlers use to report a problem. See Client-Side Tools.If the whole reply fails, every card still showing .executing is switched to .failure, so no card spins forever.

ConversationListState

@MainActor @Observable final class ConversationListState A list of the user’s past conversations, loaded a page at a time.

load

Loads the first page and replaces conversations. Use it for the first load and for pull to refresh.

loadMore

Adds the next page to the list. Does nothing when hasMore is false or a page is already loading.

clearError

Using UIKit

Neither class is tied to SwiftUI. From UIKit you can watch them with withObservationTracking, or skip them and call client.send(_:conversationId:configure:) yourself, switching to the main thread inside the callbacks:

Streaming

The send and callback API underneath

Client-Side Tools

What fills in the tool cards

Conversations & History

The paging behind these classes

Error Handling

Turning state.error into a good message