# Quickstart

StreamChatAI gives your iOS app the screens of an AI assistant: a conversation that streams the agent's replies, a prompt composer with dictation and photos, the agent's reasoning and tool calls, and a face for a voice agent. The agent behind them runs on [Stream Agents](https://getstream.io/agents/docs/quickstart/?lang=ios): you describe it once, with its instructions, knowledge, tools and voice, and your app talks to it with the `StreamAgents` SDK.

This page shows what you can build, how the pieces fit together, and takes you from an empty project to a working assistant.

## What you can build

| You want to build                                                                               | Start with                                                                                                        |
| ----------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| An AI-first app, like ChatGPT or Claude: one user, one assistant, many conversations            | This page, then the [Personal assistant](https://getstream.io/chat/docs/sdk/ios/ai-components/use-cases/personal-assistant/) use case |
| A support agent that answers from your help center, looks up orders and hands over to your team | The [Customer support agent](https://getstream.io/chat/docs/sdk/ios/ai-components/use-cases/customer-support-agent/) use case         |
| A voice agent the user talks to, with a visual presence and a written transcript                | The [Voice agent](https://getstream.io/chat/docs/sdk/ios/ai-components/use-cases/voice-agent/) use case                               |
| An agent that shows its work and asks before it acts                                            | [Reasoning and Tool Calls](https://getstream.io/chat/docs/sdk/ios/ai-components/reasoning-and-tool-calls/)                            |
| An agent that uses the device: the calendar, the location, a screen of your app                 | The [Personal assistant](https://getstream.io/chat/docs/sdk/ios/ai-components/use-cases/personal-assistant/) use case                 |
| Replies that are UI, such as a trip plan with a map and a form                                  | [Generative UI](https://getstream.io/chat/docs/sdk/ios/ai-components/generative-ui/)                                                  |

Voice runs through all of them. Every composer has a microphone button that dictates into it, and the same agent can hold the conversation out loud: `VoiceSession` puts it on a call with the user, `AIVoiceGlowView` or `AIVoiceWaveView` shows what it's doing, and `AIConversationView` writes out what was said.

## How it fits together

Your app never talks to the model, and it doesn't run the agent. It opens a **session** with your agent and asks it things. The agent runs on Stream's router, which calls the model, runs the agent's tools and writes the conversation as it goes, and the components show it:

```mermaid
sequenceDiagram
    participant App as iOS app
    participant Router as Stream Agents
    participant LLM as Model
    App->>Router: agents.chat() with StreamAgents
    App->>Router: responses.create("How do I...?")
    Router->>LLM: The conversation so far
    LLM-->>Router: Reasoning, tool calls, tokens
    Router-->>App: The reply as it streams, with its steps
    Note over App: AIConversationView shows it
```

- **StreamAgents** signs the user in, opens and lists sessions, asks, stops a reply, answers the questions of tools that ask first, and runs the tools your app gives the agent on the phone.
- **StreamChatAI** shows it all: `AIConversationView` and `AIComposerView`, the reasoning and tool calls of each reply, and the voice visualizations.
- **Your agent** lives in a folder of plain files that you sync with the Stream CLI: its instructions, knowledge, skills, tools, connectors and models.

For voice, `VoiceSession` starts the agent on a call and joins it from the phone. The agent hears the user and answers out loud, and what is said is written into the session's conversation.

## Before you start

- A Stream app, from the [Stream Dashboard](https://getstream.io/signup/?product=chat).
- The beta [Stream CLI](https://getstream.io/cli/docs/install/), which has the `getstream agents` commands.
- Xcode 27 or later, and an app that targets iOS 15 or later.

## Step 1: Create the agent

Install the beta CLI, pick your Stream app, and make an agent from the text template:

```sh
curl -fsSL https://getstream.io/cli/beta/install.sh | bash
getstream init                                     # pick or create a Stream app
getstream agents init my-agent --template text     # or --template voice
```

That creates `agents/my-agent/`, with the agent's settings in `agent.yaml` and its system prompt in `instructions.md`. Write what the agent is for in `instructions.md`, then sync it to Stream and try it in the terminal:

```sh
getstream agents sync my-agent
getstream agents test my-agent
```

The [Stream Agents quickstart](https://getstream.io/agents/docs/quickstart/?lang=ios) covers the agent's folder: knowledge, skills, tools, connectors and guardrails.

## Step 2: Install the SDKs

Add two packages to your app: `StreamAgents`, the client for your agent, and `StreamChatAI`, the views. In Xcode, select "Add Package Dependencies..." in the File menu for each, or add them to your package file:

```swift
dependencies: [
    .package(url: "https://github.com/GetStream/Vision-Agents.git", branch: "accelerate"),
    .package(url: "https://github.com/GetStream/stream-chat-swift.git", from: "5.16.0")
],
targets: [
    .target(
        name: "YourApp",
        dependencies: [
            .product(name: "StreamAgents", package: "Vision-Agents"),
            .product(name: "StreamChatAI", package: "stream-chat-swift")
        ]
    )
]
```

`StreamAgents` brings Stream Chat and Stream Video with it, which keep the conversation and carry the voice. SwiftPM clones the whole Vision-Agents repository the first time, which takes a while.

Is your agent on a backend of your own? Add only `StreamChatAI`, and connect the views to your agent with a transport, as described in [Conversation State](https://getstream.io/chat/docs/sdk/ios/ai-components/conversation-state/#write-a-transport). StreamChatAI doesn't depend on the other Chat libraries, so it works next to `StreamChatSwiftUI` or a UI of your own. If your agent answers with [generative UI](https://getstream.io/chat/docs/sdk/ios/ai-components/generative-ui/), also add `StreamChatA2UI` or `StreamChatOpenUI`.

The composer can attach photos and dictate, so add these keys to your `Info.plist`, each with a description of why your app needs it:

- `NSCameraUsageDescription` and `NSPhotoLibraryUsageDescription`, for photos.
- `NSMicrophoneUsageDescription` and `NSSpeechRecognitionUsageDescription`, for dictation and voice.

## Step 3: Sign the user in

Create one `StreamAgents` client for your app with your API key, and set the user once they sign in. The user's Stream token comes from your backend, which mints it with your API secret, as described in [authentication](https://getstream.io/docs/platform/authentication/). The client asks for it again when it expires:

```swift
import StreamAgents

let agents = StreamAgents(apiKey: "<# Your API key #>")

func signIn(userID: String, name: String) {
    agents.setUser(User(id: userID, name: name)) {
        try await yourBackend.streamToken()
    }
}
```

To try things out before you have a backend, mint a token for a test user with the CLI, `getstream token jane --ttl 2h`, and pass it as `agents.setUser(User(id: "jane"), token: "...")`.

## Step 4: Connect a conversation to the agent

`AIConversation` holds a conversation's state and drives the views, and a **transport** connects it to your agent. Add these two types to your project. `AgentMessage` is one message, as the views show it:

```swift
import StreamChat
import StreamChatAI

struct AgentMessage: AIMessage {
    let message: ChatMessage

    var id: String { message.id }
    var role: AIMessageRole { message.extraData["ai_generated"]?.boolValue == true ? .assistant : .user }
    var text: String { message.text }
    var isGenerating: Bool { message.extraData["generating"]?.boolValue == true }
    // The reasoning and tool calls the agent writes on its reply.
    var parts: [AIMessagePart] {
        AIMessagePart.parts(from: message.allAttachments.map { ($0.type.rawValue, $0.payload) })
    }
}
```

`AgentTransport` asks the agent, stops it, and shows the conversation as the agent writes it. Its `approver` answers the tools that ask before they run:

```swift
import Combine
import StreamChat
import StreamChatAI
import StreamAgents

@MainActor
final class AgentTransport: AIConversationTransport {
    let session: AgentSession
    private let channel: ChatChannelController
    private var subscription: AnyCancellable?

    init(_ session: AgentSession) async throws {
        self.session = session
        // The agent writes the conversation into a channel, which the views read.
        channel = try await session.chat()
        // Follows the agent, and runs the tools the app gives it.
        await session.start()
    }

    func connect(_ feed: AIConversationFeed<AgentMessage>) {
        let publish = { [channel] in
            feed.update(messages: channel.messages.reversed().map(AgentMessage.init))
        }
        subscription = channel.messagesChangesPublisher.sink { _ in publish() }
        channel.synchronize { _ in publish() }
    }

    func send(_ request: AIConversationRequest<AgentMessage>) async throws {
        _ = try await session.responses.create(request.text)
    }

    func stop() async throws {
        try await session.interrupt()
    }

    var approver: AIToolApprover {
        AIToolApprover(userID: agents.backend.user?.id ?? "", clientID: AIClientIdentity.installID) { call, allowed in
            try await self.session.decide(call.id, allowed: allowed)
        }
    }
}
```

## Step 5: Show the conversation

Open a session with your agent, and show it with `AIConversationView` and the composer:

```swift
import StreamChatAI
import SwiftUI
import StreamAgents

struct AssistantScreen: View {
    @State private var transport: AgentTransport?

    var body: some View {
        if let transport {
            AssistantView(transport: transport)
        } else {
            ProgressView()
                .task { transport = try? await AgentTransport(agents.chat(agent: "my-agent")) }
        }
    }
}

struct AssistantView: View {
    @StateObject private var conversation: AIConversation<AgentMessage>

    init(transport: AgentTransport) {
        _conversation = StateObject(wrappedValue: AIConversation(transport: transport, approver: transport.approver))
    }

    var body: some View {
        AIConversationView(conversation: conversation)
            .safeAreaInset(edge: .bottom) {
                AIComposerView(conversation: conversation)
            }
    }
}
```

Run the app and ask something. The agent thinks, then its reply writes itself out, with Markdown, code and charts, and with the reasoning and tool calls that led to it. While it writes, the send button is a stop button, and under each finished reply the user can copy, share and rate it.

The [Conversation View](https://getstream.io/chat/docs/sdk/ios/ai-components/conversation-view/) guide covers what else the screen can show, such as suggestions while the conversation is empty.

## Step 6: Add voice

The composer already takes voice: the microphone button in its text field dictates into it with Apple's Speech framework.

To let the user talk to the agent instead, start a voice session. The agent joins a call right away, and the phone joins it with the microphone on:

```swift
let voice = try await VoiceSession.start(agents: agents, agent: "my-agent")
await voice.session.start()
await voice.join()
```

Give the agent a face with `AIVoiceGlowView`, which moves differently while the agent is connecting, listening, thinking and speaking. The [Voice agent](https://getstream.io/chat/docs/sdk/ios/ai-components/use-cases/voice-agent/) use case builds the whole screen: the agent's state, the visualizations, mute and hang up, and what was said written out as a conversation. An agent made with `--template voice` also lets you pick its speech-to-text and text-to-speech models, its voice and its greeting.

## Theming

The components are styled with `AIAppearance`. It builds on `DesignSystemTokens`, the color, layout and typography tokens shared by Stream's SDKs, and adds the colors, fonts and icons of the AI components. Set it once, before the components first show, for example when your app starts:

```swift
let tokens = DesignSystemTokens()
tokens.colors.accentPrimary = .systemPurple

let appearance = AIAppearance(tokens: tokens)
appearance.colors.reasoningRule = .systemPurple
appearance.colors.suggestionBackground = .systemMint.withAlphaComponent(0.3)
appearance.fonts.suggestion = .footnote
appearance.images.composerSend = Image(systemName: "paperplane.circle.fill")

InjectedValues[\.aiAppearance] = appearance
```

The AI colors and fonts are derived from the tokens, so change the tokens before you create the appearance. StreamChatAI re-exports StreamCore and `DesignSystemTokens`, so this code needs no other import besides SwiftUI.

The colors, fonts and icons are named after the component that uses them, such as `composerBackground` or `toolCallAccent`, and each component's guide lists its own under **Appearance**. The syntax highlighting colors (`code...`) have light and dark variants.

### Localization

Every text the components show, such as the composer's "Ask anything" placeholder or "Thought for 12s", goes through `AIAppearance.localizationProvider`, which reads the library's own `Localizable` strings table by default. Replace it to change or translate the texts. For example, to read your own strings first and fall back to the library's:

```swift
let libraryText = AIAppearance.localizationProvider
AIAppearance.localizationProvider = { key, table in
    let text = Bundle.main.localizedString(forKey: key, value: nil, table: "StreamChatAI")
    return text == key ? libraryText(key, table) : text
}
```

With this provider, a `StreamChatAI.strings` table in your app overrides the keys it lists, such as `"composer.placeholder.ask_anything" = "Message the assistant";`, and every other text stays as it is. The keys are listed in the library's [`Localizable.strings`](https://github.com/GetStream/stream-chat-swift/blob/develop/Sources/StreamChatAI/Resources/en.lproj/Localizable.strings).

## The components

| Component                                                            | What it does                                                               | Guide                                                                                                      |
| -------------------------------------------------------------------- | -------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| `AIConversationView`                                                 | Lays out a conversation the way AI assistants do                           | [Conversation View](https://getstream.io/chat/docs/sdk/ios/ai-components/conversation-view/)                                   |
| `AIConversation`                                                     | Holds a conversation's state, and reaches your agent through a transport   | [Conversation State](https://getstream.io/chat/docs/sdk/ios/ai-components/conversation-state/)                                 |
| `AIComposerView`, `SpeechToTextButton`                               | Takes what the user writes, with photos, chat options and dictation        | [Composer](https://getstream.io/chat/docs/sdk/ios/ai-components/composer/)                                                     |
| `StreamingMessageView`, `AITypingIndicatorView`                      | Types out Markdown, code and charts as they stream                         | [Streaming Messages](https://getstream.io/chat/docs/sdk/ios/ai-components/streaming-messages/)                                 |
| `AIMessagePartsView`, `StreamingReasoningView`, `AIToolApprovalCard` | Shows a reply's reasoning and tool calls, and asks before a tool runs      | [Reasoning and Tool Calls](https://getstream.io/chat/docs/sdk/ios/ai-components/reasoning-and-tool-calls/)                     |
| `AIClientToolRunner`                                                 | Runs the tools the agent asks the device to run                            | [Reasoning and Tool Calls](https://getstream.io/chat/docs/sdk/ios/ai-components/reasoning-and-tool-calls/#tools-on-the-device) |
| `AIUIHost`                                                           | Shows UI the agent writes in place of a reply's text                       | [Generative UI](https://getstream.io/chat/docs/sdk/ios/ai-components/generative-ui/)                                           |
| `AIVoiceGlowView`, `AIVoiceWaveView`                                 | Shows what a voice agent is doing                                          | [Voice Visualizations](https://getstream.io/chat/docs/sdk/ios/ai-components/voice-visualizations/)                             |
| `SuggestionsView`, `SidebarView`                                     | Offers conversation starters, and puts a side menu beside the conversation | [Conversation View](https://getstream.io/chat/docs/sdk/ios/ai-components/conversation-view/)                                   |
| `AIOnDeviceModel`                                                    | Answers with Apple's on-device model when your agent can't                 | [Conversation State](https://getstream.io/chat/docs/sdk/ios/ai-components/conversation-state/#answer-on-the-device)            |
| `AIAppearance`                                                       | Sets the colors, fonts, icons and texts of all of them                     | [Theming](#theming)                                                                                        |

## Next steps

- [Customer support agent](https://getstream.io/chat/docs/sdk/ios/ai-components/use-cases/customer-support-agent/): answers from your help center, order lookups, a refund the user confirms, and a handover to your team.
- [Personal assistant](https://getstream.io/chat/docs/sdk/ios/ai-components/use-cases/personal-assistant/): a list of conversations, the calendar and reminders as tools on the phone, and a voice mode.
- [Voice agent](https://getstream.io/chat/docs/sdk/ios/ai-components/use-cases/voice-agent/): a voice conversation with the agent as a glowing globe, and what was said written out.
- [Stream Agents](https://getstream.io/agents/docs/quickstart/?lang=ios): the agent itself, its tools, knowledge, connectors and phone numbers.

---

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/ios/ai-components/](https://getstream.io/chat/docs/sdk/ios/ai-components/).