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

# Chat & Streaming

> Send messages, stream replies, and read tool cards in the Desktop app

Every reply streams live from your local agent — text, reasoning, tool cards, and usage all arrive as typed events over `127.0.0.1`.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import Agent

agent = Agent(
    name="Assistant",
    instructions="You are a helpful assistant.",
)
# The Desktop app streams this agent's reply token-by-token.
agent.start("Write a haiku about the sea", stream=True)
```

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    You[👤 You] --> Msg[💬 Message]
    Msg --> Engine[🧠 Engine]
    Engine --> Stream[📡 SSE Events]
    Stream --> Turn[✅ Rendered Turn]

    classDef you fill:#8B0000,stroke:#7C90A0,color:#fff
    classDef msg fill:#6366F1,stroke:#7C90A0,color:#fff
    classDef engine fill:#189AB4,stroke:#7C90A0,color:#fff
    classDef stream fill:#F59E0B,stroke:#7C90A0,color:#fff
    classDef turn fill:#10B981,stroke:#7C90A0,color:#fff

    class You you
    class Msg msg
    class Engine engine
    class Stream stream
    class Turn turn
```

## Quick Start

<Steps>
  <Step title="Send your first message">
    Type in the composer and press `Enter`. The reply streams in as the agent produces it.
  </Step>

  <Step title="Watch the events render">
    Text, a live reasoning panel, tool cards, and a usage line all appear in the same turn as they stream.
  </Step>

  <Step title="Act on the turn">
    Hover a turn to **Copy**, **Regenerate**, **Fork**, or **Delete** it.
  </Step>
</Steps>

***

## How It Works

The Desktop app opens `POST /chat` and streams these 11 event types straight into the turn — you write no client code for it.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import Agent

agent = Agent(
    name="Assistant",
    instructions="You are a helpful assistant.",
)
# The Desktop app opens POST /chat and streams these 11 event types
# straight into the turn — you write no client code for it.
agent.start("Summarize today's notes", stream=True)
```

The engine sends Server-Sent Events on `POST /chat`.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
sequenceDiagram
    participant You as 👤 You
    participant App as 🌐 App
    participant Engine as 🧠 Engine
    participant Agent as 🤖 Agent

    You->>App: type message + Enter
    App->>Engine: POST /chat (prompt)
    Engine->>Agent: agent.start(stream=True)
    Agent-->>Engine: chunks
    Engine-->>App: start / delta / tool_call / usage / end
    App-->>You: streamed turn
```

Each event maps to one visible piece of the turn:

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    Send[💬 Send] --> Start[🟢 start]
    Start --> Reason[🧠 reasoning]
    Reason --> Delta[✍️ delta]
    Delta --> Draft[🔧 tool_drafting]
    Draft --> Call[🔨 tool_call]
    Call --> Ask[🔑 approval_request]
    Ask --> Result[📦 tool_result]
    Result --> Use[📊 usage]
    Use --> End[✅ end]

    classDef input fill:#6366F1,stroke:#7C90A0,color:#fff
    classDef middle fill:#F59E0B,stroke:#7C90A0,color:#fff
    classDef success fill:#10B981,stroke:#7C90A0,color:#fff

    class Send input
    class Start,Reason,Delta,Draft,Call,Ask middle
    class Result,Use,End success
```

Every event carries a `msg_id` that scopes it to a specific assistant message; `start` additionally carries a `run_id` that scopes it to a specific run, so a `POST /approve/{aid}` or `POST /cancel` sent later addresses the right run and not whichever one happens to be pending. The vocabulary is exactly the **11 events** below — a `StreamProtocolVocabulary` test in the engine fails CI if a new emitter or a new documented event ever drifts from that list, so this table stays a faithful mirror of the wire.

Every event the stream emits (from `engine/server.py`), with the exact payload a client sees on the SSE wire:

| Event | Payload | Rendered as |
| - | - | - |
| `start` | `{msg_id, run_id}` | New assistant turn container |
| `reasoning` | `{msg_id, text}` (incremental) | Live "Thinking… Xs" panel (collapses to "Thought for Xs") |
| `delta` | `{msg_id, text}` | Streamed text into the current turn |
| `tool_drafting` | `{msg_id, name}` | "Preparing tool…" state / argument preview inside a tool card |
| `tool_call` | `{msg_id, call_id, name, args}` | Tool card (running dot) |
| `tool_result` | `{msg_id, call_id, name, ok, output, seconds}` | Tool card result pane (tail-truncated with **Show all** + **Copy output**) |
| `approval_request` | `{msg_id, approval_id, call_id, name, args}` | [Approval card](/docs/features/desktop/approvals) (Allow / Always allow / Deny) — human-in-the-loop gate |
| `usage` | `{msg_id, chars, seconds, ttft}` | Chars, seconds, time-to-first-token under the reply |
| `cancelled` | `{msg_id, run_id}` | "Stopped" indicator |
| `error` | `{msg_id, message, kind}` | Typed error card — `auth` / `rate_limit` / `no_answer` / `empty` / `internal` |
| `end` | `{msg_id, user_index, assistant_index, versions, active}` | Finalizes the turn |

Every event carries `msg_id` so the client can address a specific message rather than assuming the last one is live. Clients integrating with the SSE stream should handle **all 11** events — a client written against a subset silently ignores the rest, and `approval_request` in particular will block the run invisibly until the 300 s [approval timeout](/docs/features/desktop/approvals) if the client does not render it.

<Note>
  Silence is treated as a failure, and the two shapes are reported differently:

  * **`empty`** — nothing at all happened (no text, no tools). Still reported as **"No output"**.
  * **`no_answer`** — the tools ran but the model produced no follow-up answer. Reported as **"The tools ran, but no answer came back"** and lists how many tool calls succeeded above.

  Tool cards remain rendered even when the answer never arrives — the work isn't hidden by a failure banner.
</Note>

***

## Capabilities

<AccordionGroup>
  <Accordion title="Reasoning panel">
    When the model emits reasoning, a live "Thinking… Xs" panel shows it and collapses to "Thought for Xs" when the turn ends. Toggle it with **Show reasoning** and **Collapse reasoning by default** in Settings.
  </Accordion>

  <Accordion title="Tool cards">
    A `tool_call` opens a card with a running dot; `tool_drafting` previews the arguments; `tool_result` fills the result pane. Long output is tail-truncated with **Show all** and **Copy output**. Tool cards remain visible even if the model produces no follow-up answer — a `no_answer` banner then explains what didn't come back.
  </Accordion>

  <Accordion title="Attachments">
    Drop a file and the composer branches on its MIME type — images and PDFs reach a vision model as bytes, everything else folds into the prompt as text.

    ```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    graph TB
        Drop[📎 File dropped] --> Mime{MIME type?}
        Mime -->|image/* or application/pdf| Vision[🖼️ base64 data: URL<br/>→ agent.start attachments=]
        Mime -->|anything else| Text[📄 Clip to 100 kB<br/>→ fold into prompt]
        Vision --> Model[🧠 Vision model sees bytes]
        Text --> Model

        classDef input fill:#6366F1,stroke:#7C90A0,color:#fff
        classDef decision fill:#F59E0B,stroke:#7C90A0,color:#fff
        classDef process fill:#189AB4,stroke:#7C90A0,color:#fff
        classDef result fill:#10B981,stroke:#7C90A0,color:#fff

        class Drop input
        class Mime decision
        class Vision,Text process
        class Model result
    ```

    Drag-drop or use the **+** button. Chips show each file's size. Limits: **2 MB max**, up to **5 files**.

    * **Images (`image/*`) and PDFs** are read as base64 and passed to the agent as `attachments=[...]`, so a vision-capable model (`gpt-4o`, `claude-3-*`, `gemini-*`) sees the image itself — not a text description of it. Point the Desktop app at a text-only model and the image is ignored silently.
    * **Text files** are clipped to **100 kB** of text and folded into the prompt, so long pastes condense into an attachment instead of filling the context.

    Only text is stored in the turn's history; the image bytes are ephemeral to the turn.

    <Note>
      Point Desktop at a vision-capable model (`gpt-4o`, `claude-3-7-sonnet-*`, `gemini-2.0-flash`, …) if you want it to actually see dropped images. A text-only model still runs, but the image never reaches it.
    </Note>
  </Accordion>

  <Accordion title="Cancel, regenerate, fork">
    **Stop** cancels a live run — the client is told explicitly with a `cancelled` event rather than inferring it from silence. Cancelling with **Stop** no longer double-reports as "No output" — a `cancelled` event is the only banner the user sees. Per-turn hover actions cover **Copy**, **Regenerate**, **Fork** (`POST /fork/{cid}/{idx}`), and **Delete message** (`DELETE /messages/{cid}/{idx}`).
  </Accordion>
</AccordionGroup>

***

## Message Versions

Regenerating an answer keeps the previous one as a prior version on the same message, so you can compare without copying anything out.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    Answer[💬 Answer v1] --> Regen[🔁 Regenerate]
    Regen --> V2[💬 Answer v2]
    V2 --> Pick[◀ 2/2 ▶ picker]

    classDef msg fill:#8B0000,stroke:#7C90A0,color:#fff
    classDef act fill:#189AB4,stroke:#7C90A0,color:#fff
    classDef pick fill:#10B981,stroke:#7C90A0,color:#fff

    class Answer,V2 msg
    class Regen act
    class Pick pick
```

* **Regenerate** sends `POST /chat` with `regenerate_of` set to the message index, so the new answer is filed as another version instead of a duplicate question.
* A `< 1/2 >` picker appears on hover once a message has more than one version, opening at the newest.
* Stepping through the picker calls `POST /version/{cid}/{idx}/{n}` and repaints the chosen answer.
* In the server payload every assistant row exposes `versions`, `active`, `version_count`, and `version_active`.

<Note>
  A message written before versioning has no `versions` key and is its own single version — nothing to pick between, so no picker shows.
</Note>

***

## Message Queue

Typing while a turn is running queues your next message instead of interrupting it.

* The queued message sends automatically when the current turn finishes.
* **Stop** clears the queue rather than sending it — cancelling means "not this either".

***

## In-App Confirmation Dialogs

Destructive actions — delete conversation, delete-all, delete message — route through an in-app modal, not the browser's `window.confirm`.

<Warning>
  `window.confirm` returns `false` immediately in WKWebView, which silently cancelled every destructive action. The in-app dialog is why **Delete conversation** now actually works. The `confirm_delete` setting gates whether the dialog appears at all.
</Warning>

***

## Keyboard Shortcuts

| Key | Action |
| - | - |
| `⌘N` | New chat |
| `⌘K` | Search all conversations |
| `⌘,` | Open settings |
| `Enter` | Send |
| `Shift+Enter` | Newline |
| `Esc` | Close overlay |

***

## Related

<CardGroup cols={2}>
  <Card title="Approvals & Safety" icon="shield-check" href="/docs/features/desktop/approvals">
    How tool calls are approved before they run
  </Card>

  <Card title="Conversations & Search" icon="magnifying-glass" href="/docs/features/desktop/conversations">
    Fork, delete, projects, and `⌘K` search
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.