> ## 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

**1. Send your first message**

Type in the composer and press `Enter`. The reply streams in as the agent produces it.

**2. Watch the events render**

Text, a live reasoning panel, tool cards, and a usage line all appear in the same turn as they stream.

**3. Act on the turn**

Hover a turn to **Copy**, **Regenerate**, **Fork**, or **Delete** it.

***

## 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](https://praison.ai/docs/features/desktop/approvals.md) (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](https://praison.ai/docs/features/desktop/approvals.md) 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.

***

## Capabilities

> [accordion] 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] 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] 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.

> [accordion] 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}`).

***

## 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.

***

## 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.

***

## Keyboard Shortcuts

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

***

## Related

> [card] [Approvals & Safety](https://praison.ai/docs/features/desktop/approvals.md)
>
> How tool calls are approved before they run

> [card] [Conversations & Search](https://praison.ai/docs/features/desktop/conversations.md)
>
> Fork, delete, projects, and `⌘K` search


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