Custom backend¶
Every backend is a ChatBackend: a function from the conversation to a stream of ChatEvents.
Map your protocol onto those events and all the elements work unchanged: streaming Markdown, tools,
approvals, sub-agents, plans. ai-elements-chat and ai-elements-ui are all you need.
class MyBackend(private val client: MyClient) : ChatBackend {
override fun stream(history: List<Message>): Flow<ChatEvent> = flow {
client.run(history.last().text).collect { e ->
when (e) {
is MyText -> emit(ChatEvent.TextDelta("answer", e.chunk))
is MyToolCall -> emit(ChatEvent.ToolInputAvailable(e.id, e.name, e.argsJson, kind = ToolKind.Function))
is MyToolResult -> emit(ChatEvent.ToolOutput(e.id, e.text))
is MyHandoff -> emit(ChatEvent.ToolInputAvailable(e.id, "handoff", "{}", kind = ToolKind.Delegation(e.agent, e.task)))
}
}
emit(ChatEvent.Finish)
}
}
The contract¶
stream(history)receives the whole conversation, ending with the new user message.- The flow is cold and collected by
ChatController; cancelling it (the user pressed stop) must cancel the request. - Throw on transport failures (
ChatBackendExceptioncarries an HTTP status); the controller shows the error. EmitChatEvent.Errorfor errors the server reports in the stream. ChatEvent.Finishis optional: a completed flow also ends the turn.- Keep per-conversation protocol state (a thread, task or session id) in the reply with
ChatEvent.Metadata; the next turn finds it in the last assistant message'smetadata.
Events¶
The events map onto the AI SDK UI Message Stream chunk types:
| Event | Becomes |
|---|---|
TextDelta / TextEnd |
a text part (by id) |
ReasoningDelta / ReasoningEnd |
a reasoning part |
ToolInputStart / ToolInputDelta / ToolInputAvailable |
a tool call and its arguments (title, kind, source) |
ToolOutput (optionally preliminary) / ToolError |
its result |
ToolApprovalRequest / ToolApproved / ToolDenied |
its approval; see Human in the loop |
SubagentUpdate |
a nested run inside a delegating tool call |
SourceUrl, File |
sources and generated files |
Data |
a data part; a later one with the same id replaces it |
Metadata, Usage |
message metadata and token usage |
These are in-process types: never send them over the network as a protocol of their own.