Skip to main content

How Streaming Works

The SDK has one streaming method. send(_:conversationId:configure:) is an async throws function. It calls your callbacks as text arrives, then returns the finished ChatResponse when the reply is complete.
  • While it runs: your onTextDelta, onToolCall, and onToolResult callbacks fire.
  • When it succeeds: it returns a ChatResponse.
  • When it fails: it throws.
The same call also runs your tools. If the agent asks for a tool, the SDK runs your handler, sends the result back, and continues the reply. See Client-Side Tools.
If you are building a SwiftUI chat screen, you usually do not call send yourself. ConversationState connects these callbacks to a message list for you.

send

Sends a message, streams the reply, runs any registered tools, and returns the finished ChatResponse.
String
required
The message to send to the agent.
String?
Continue an existing conversation. nil always starts a new conversation.
@Sendable (inout StreamCallbacks) -> Void
A closure where you set the callbacks you want. Set only the ones you need.
Unlike the Android SDK, passing conversationId: nil does not fall back to currentConversationId. It starts a new conversation. To continue one, pass the ID yourself, or use ConversationState, which keeps track of it.

StreamCallbacks

Callbacks you do not set are ignored. There is no onStart, onFinish, or onError callback. The async throws signature covers all three:
Callbacks do not run on the main thread. Switch to the main actor before touching your UI:
The SDK waits for each callback to finish before reading more of the reply, so a slow callback slows the whole stream. Keep them short.

Showing text as it arrives

ChatViewModel is marked @MainActor, so await self?.append(chunk) moves to the main thread for you.

Stopping a Reply

Cancelling the Task around send closes the connection.
Cancelling stops your app from reading the reply. The agent may still finish on the server, and the partial message is saved. The text you already received is still correct, and it will be there when you next call listMessages.

Continuing a Conversation

The client remembers the last conversation in currentConversationId, but you have to pass it back in to continue:
To start fresh, pass nil (the default), or clear the saved ID:
See Conversations & History for listing conversations and loading old messages, and SwiftUI for state that tracks the conversation ID for you.

retry

Ask the agent to answer again. Streams and runs tools exactly like send.
String
required
The conversation the message belongs to.
String
required
The ID of the agent message to redo.
@Sendable (inout StreamCallbacks) -> Void
The same callbacks as send.
The old message and everything after it are replaced. Remove those messages from your UI before the new reply starts. ConversationState.retry(messageId:) does this for you.

sendNonStreaming

Sends a message and returns the whole reply at once. No callbacks, and no tools.
String
required
The message to send to the agent.
String?
Continue an existing conversation. nil starts a new one.
Your registered tools do not run. If the agent asks for a tool, you get back finishReason == .toolCalls and no answer. Use send for any agent that has client-side Custom Actions.
Use sendNonStreaming when you do not need to show text as it arrives, such as a background summary or a scripted first message.

Which Method to Use

Client-Side Tools

Let the agent run functions on the device

Streaming Types

StreamCallbacks, ToolCallInfo, and more

ChatResponse

What a finished reply contains

Error Handling

Error types and how to handle them