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

# Streaming overview

> Stream chat runs, agent status, and other live events through the SDK.

<Note>
  For a complete working application — send, live cell streaming, and reload
  re-attach — see the
  [chat-demo example](https://github.com/TextQLLabs/textql-ts-sdk/tree/main/examples/chat-demo).
</Note>

Streaming is currently available in the **TypeScript SDK** (`@textql/sdk`
v1.2.2+). All examples below are TypeScript.

The TextQL API exposes several **server-streaming** endpoints that are not part
of the REST surface documented elsewhere in this reference. They are consumed
through the SDK's streaming client, which speaks
[Connect-RPC](https://connectrpc.com) to the same gateway with the same API key.

```bash theme={null}
npm install @textql/sdk
```

## Quickstart

```typescript theme={null}
import { createStreamingClient } from "@textql/sdk/streaming";

const streaming = createStreamingClient({ apiKey: "<your-api-key>" });
const chatId = "<your-chat-id>";

// Watch a chat: cell snapshots plus run lifecycle events
for await (const event of streaming.chats.watchChat({ chatId })) {
  switch (event.payload.case) {
    case "runStarted":  /* a run is executing */ break;
    case "cell":        /* event.payload.value is a Cell snapshot */ break;
    case "runComplete": /* run finished */ break;
    case "runError":    /* event.payload.value.error */ break;
    case "heartbeat":   break; // keepalive — safe to ignore
  }
}
```

Streams are async iterables. Pass an `AbortSignal` to cancel:

```typescript theme={null}
const abort = new AbortController();
for await (const update of streaming.agents.streamAgentStatus(
  {},
  { signal: abort.signal },
)) {
  console.log(update.agentId, update.status);
}
```

`serverURL` defaults to `https://app.textql.com` — on-prem deployments pass
their own host and the SDK routes RPCs correctly from there.

## Streaming Methods

| Method                                                                                                                      | Emits                                                                                                      |
| --------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| [`chats.watchChat({ chatId, latestCompleteCellId? })`](/api-reference/sdk/streaming/watch-chat)                             | `WatchChatEvent`: `opened`, `runStarted`, `cell`, `runComplete`, `runError`, `handoffPending`, `heartbeat` |
| [`chats.streamChat({ chatId, latestCompleteCellId? })`](/api-reference/sdk/streaming/stream-chat)                           | `Cell` snapshots while a run executes; the stream ends when the run does                                   |
| [`agents.streamAgentStatus({})`](/api-reference/sdk/streaming/agent-status)                                                 | `AgentStatusUpdate` for every visible agent run transition                                                 |
| [`apps.streamAppActivity({ appId })`](/api-reference/sdk/streaming/app-activity)                                            | `AppActivityStreamEvent`: activity batches, presence, heartbeat                                            |
| [`dashboards.watchDashboardHealth({ dashboardId })`](/api-reference/sdk/streaming/dashboard-health)                         | `DashboardHealthEvent` on health transitions                                                               |
| [`playbooks.streamTemplateDataStatus({ templateHeaderId, playbookId })`](/api-reference/sdk/streaming/template-data-status) | `TemplateDataStatusUpdate` per template-data row                                                           |

## watchChat vs streamChat

Both deliver the same cell snapshots from the same event bus; they differ in
what they tell you about run state.

* **`watchChat`** is a chat-scoped subscription: it announces `runStarted`
  immediately when a run is live, delivers cells, signals `runComplete` /
  `runError`, and sends heartbeats every 20 seconds so idle connections
  survive proxies. It stays open across runs. Pass your last known cell id as
  `latestCompleteCellId` to replay anything you missed — this is how a UI
  re-attaches to a run after a page reload.
* **`streamChat`** is a run-scoped firehose: cells only, ending when the run
  ends. Right for one-shot scripts (`send → for await cells → exit`) that
  don't need lifecycle state.

Rule of thumb: UIs and long-lived consumers watch; one-shot scripts stream.

## Driving a Chat End to End

The [chat-demo example app](https://github.com/TextQLLabs/textql-ts-sdk/tree/main/examples/chat-demo)
implements this full flow (plus reload re-attach via `latestCompleteCellId`);
the condensed version:

```typescript theme={null}
import { Textql } from "@textql/sdk";
import { createStreamingClient } from "@textql/sdk/streaming";

const serverURL = "https://app.textql.com/rpc/public";
const client = new Textql({ apiKey, serverURL });
const streaming = createStreamingClient({ apiKey, serverURL });

const created = await client.chats.createChat({ body: { /* model, paradigm */ } });
const chatId = created.chat!.id!;
const sent = await client.chats.send({ body: { chatId, message: "..." } });

for await (const event of streaming.chats.watchChat({
  chatId,
  latestCompleteCellId: sent.cellId,
})) {
  if (event.payload.case === "cell") render(event.payload.value);
  if (event.payload.case === "runComplete") break;
}
```

Each method above links to its TypeScript reference — signature, worked
example, and request/event fields. Cell-emitting streams share the [`Cell` message reference](/api-reference/sdk/streaming/cells).

## Notes

* **Runtime**: browsers and Node 18+ (server-streaming rides on `fetch`
  response streams).
* **Types**: streaming methods return fully typed objects — discriminated
  unions for event payloads, TypeScript enums for statuses. They differ
  slightly in shape from the REST models documented elsewhere in this
  reference.
* **Disconnecting vs cancelling**: aborting the signal or `break`ing out of
  the loop closes *your subscription* — **the run keeps executing server-side**.
  To actually stop a chat run, call the
  [Cancel Stream endpoint](/api-reference/sdk/chatservice/cancelstream):

  ```typescript theme={null}
  await client.chats.cancelStream({ body: { chatId } });
  ```

  You can watch the same chat again afterwards; `watchChat` reports the
  outcome via its lifecycle events.
