> ## Documentation Index
> Fetch the complete documentation index at: https://chatbase.co/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Streaming

> How to stream real-time responses from the Chatbase Android SDK using callbacks and Kotlin Flow.

## Two-Tier Streaming API

The SDK provides two levels of abstraction for streaming:

* **`sendMessage()`** — High-level API with a callback DSL. Handles tool calls automatically (up to 10 iterations). Recommended for most use cases.
* **`sendMessageStream()`** — Low-level API returning a `Flow<ChatStreamEvent>`. Tool calls are not handled automatically, giving you full control over event processing.

## sendMessage

`interface ChatbaseClient` — **Package:** `com.chatbase.sdk`

```kotlin theme={null}
suspend fun sendMessage(
    message: String,
    conversationId: String? = null,
    callbacks: StreamCallbacks.() -> Unit = {}
): ChatResponse
```

Sends a message, streams the response in real time, and automatically handles tool calls (up to 10 iterations). Returns the aggregated `ChatResponse` when the stream completes.

<ParamField body="message" type="String" required>
  The user message to send to the agent.
</ParamField>

<ParamField body="conversationId" type="String?">
  Continue an existing conversation. Omit to use `currentConversationId` or start a new one.
</ParamField>

<ParamField body="callbacks" type="StreamCallbacks.() -> Unit">
  Streaming callback DSL.
</ParamField>

```kotlin theme={null}
val response = client.sendMessage("Tell me a story") {
    onStart {
        println("Stream started...")
    }
    onTextDelta { delta ->
        // Called for each text chunk — append to your UI
        print(delta)
    }
    onToolCall { toolCall ->
        println("Agent is calling: ${toolCall.toolName}")
    }
    onToolResult { result ->
        println("Tool result: ${result.outputAsString()}")
    }
    onFinish { response ->
        println("\nDone! Message ID: ${response.id}")
    }
    onError { error ->
        println("Error: ${error.message}")
    }
}
```

### StreamCallbacks

`class StreamCallbacks` — **Package:** `com.chatbase.sdk`

<Tip>
  All callbacks are invoked on `Dispatchers.Main` — it is safe to update UI directly from any callback without explicit dispatching.
</Tip>

```kotlin theme={null}
fun onStart(handler: () -> Unit)
```

Called when the connection opens and streaming begins.

```kotlin theme={null}
fun onTextDelta(handler: (text: String) -> Unit)
```

Called for each incremental text chunk received.

```kotlin theme={null}
fun onToolCall(handler: (toolCall: ToolCallInfo) -> Unit)
```

Called when a tool call's full input is available (before execution).

```kotlin theme={null}
fun onToolResult(handler: (result: ToolResultInfo) -> Unit)
```

Called after a tool handler executes and returns a result.

```kotlin theme={null}
fun onFinish(handler: (response: ChatResponse) -> Unit)
```

Called when the stream completes successfully.

```kotlin theme={null}
fun onError(handler: (error: ChatbaseException) -> Unit)
```

Called when an error occurs during streaming.

### ChatResponse

`data class ChatResponse` — **Package:** `com.chatbase.sdk.model`

The aggregated result after streaming completes.

<ResponseField name="id" type="String" required>
  Server-assigned message ID.
</ResponseField>

<ResponseField name="role" type="String" required>
  Always `"assistant"`.
</ResponseField>

<ResponseField name="parts" type="List<Part>" required>
  The response content — text, tool calls, and tool results. See [ChatResponse](/docs/android-sdk/chat-response).
</ResponseField>

<ResponseField name="metadata" type="ResponseMetadata" required>
  <Expandable title="properties">
    <ResponseField name="messageId" type="String?">
      Same as `ChatResponse.id`.
    </ResponseField>

    <ResponseField name="userMessageId" type="String?">
      Server-assigned ID for the user's message.
    </ResponseField>

    <ResponseField name="conversationId" type="String?">
      The conversation ID. Pass this to continue the conversation.
    </ResponseField>

    <ResponseField name="finishReason" type="FinishReason">
      Why the stream ended: `STOP`, `ERROR`, `TOOL_CALLS`, or `UNKNOWN`.
    </ResponseField>

    <ResponseField name="usage" type="Usage?">
      <Expandable title="properties">
        <ResponseField name="credits" type="Double">
          Credits consumed by this request.
        </ResponseField>
      </Expandable>
    </ResponseField>
  </Expandable>
</ResponseField>

## sendMessageStream

```kotlin theme={null}
fun sendMessageStream(
    message: String,
    conversationId: String? = null
): Flow<ChatStreamEvent>
```

Returns a cold `Flow` of raw streaming events. Tool calls are **not** handled in Flow mode — registered tool handlers are not invoked, and the SDK exposes no API for submitting tool results manually.

<ParamField body="message" type="String" required>
  The user message to send to the agent.
</ParamField>

<ParamField body="conversationId" type="String?">
  Continue an existing conversation. Omit to use `currentConversationId` or start a new one.
</ParamField>

<Warning>
  Tool calls are **not** executed in Flow mode — tool events are informational only, and the stream finishes with `finishReason == "tool-calls"` without a final answer. If your agent uses client-side tools, use `sendMessage` with callbacks instead. See [Client-Side Tools](/docs/android-sdk/client-side-tools) for details.
</Warning>

```kotlin theme={null}
client.sendMessageStream("Tell me about Kotlin").collect { event ->
    when (event) {
        is ChatStreamEvent.TextDelta -> print(event.delta)
        is ChatStreamEvent.Finish -> println("\nDone: ${event.finishReason}")
        is ChatStreamEvent.Error -> println("Error: ${event.exception.message}")
        else -> { /* handle other events as needed */ }
    }
}
```

You can also filter for specific event types:

```kotlin theme={null}
client.sendMessageStream("Hello")
    .filterIsInstance<ChatStreamEvent.TextDelta>()
    .collect { event -> print(event.delta) }
```

## Stream Events

The `Flow` returned by `sendMessageStream` emits `ChatStreamEvent` objects — text deltas, tool input/output, step lifecycle, and errors. See [Streaming Events](/docs/android-sdk/streaming-events) for the full type reference.

## Continuing a Conversation

The SDK automatically tracks the current conversation. After sending a message, subsequent calls reuse the same conversation:

```kotlin theme={null}
// First message — starts a new conversation
client.sendMessage("My name is Alice.")
println(client.currentConversationId) // "conv_abc123"

// Subsequent messages continue the same conversation
client.sendMessage("What is my name?") // Agent remembers: "Alice"
```

To start a fresh conversation:

```kotlin theme={null}
client.newConversation()
client.sendMessage("Fresh start!") // Creates a new conversation
```

<Tip>
  See [Conversations & History](/docs/android-sdk/conversations) for listing conversations and loading message history.
</Tip>

## retry

```kotlin theme={null}
suspend fun retry(
    conversationId: String,
    messageId: String,
    callbacks: StreamCallbacks.() -> Unit = {}
): ChatResponse
```

Retry a failed assistant message. Same streaming and tool-loop behavior as `sendMessage`.

<ParamField body="conversationId" type="String" required>
  The conversation containing the failed message.
</ParamField>

<ParamField body="messageId" type="String" required>
  The ID of the assistant message to retry.
</ParamField>

<ParamField body="callbacks" type="StreamCallbacks.() -> Unit">
  Streaming callback DSL.
</ParamField>

A convenience extension extracts the IDs from a `ChatResponse`:

```kotlin theme={null}
suspend fun ChatbaseClient.retry(
    response: ChatResponse,
    callbacks: StreamCallbacks.() -> Unit = {}
): ChatResponse
```

```kotlin theme={null}
val response = client.sendMessage("Hello")
// ... later:
val retried = client.retry(response) {
    onTextDelta { delta -> print(delta) }
}
```

## retryStream

```kotlin theme={null}
fun retryStream(
    conversationId: String,
    messageId: String
): Flow<ChatStreamEvent>
```

Raw streaming variant of `retry`. Tool calls are **not** handled automatically.

```kotlin theme={null}
client.retryStream(conversationId, messageId).collect { event ->
    when (event) {
        is ChatStreamEvent.TextDelta -> print(event.delta)
        is ChatStreamEvent.Finish -> println("\nDone")
        is ChatStreamEvent.Error -> println("Error: ${event.exception.message}")
        else -> {}
    }
}
```

## Related

<CardGroup cols={2}>
  <Card title="Client-Side Tools" icon="bolt" href="/docs/android-sdk/client-side-tools">
    Register tool handlers the agent can invoke
  </Card>

  <Card title="Error Handling" icon="triangle-exclamation" href="/docs/android-sdk/error-handling">
    Exception hierarchy and error handling patterns
  </Card>
</CardGroup>
