eve
Version:
Filesystem-first framework for durable backend AI agents that run anywhere.
145 lines (104 loc) • 4.84 kB
text/mdx
---
title: "Streaming"
description: "Consume eve client stream events live, reconnect by event index, and aggregate turn results."
---
Every `ClientSession.send()` call posts the turn, then reads the session's NDJSON (newline-delimited JSON) event stream. `MessageResponse` gives you two ways to consume that stream, aggregating it with `result()` or iterating it live.
## Aggregate a turn
Use `result()` when you only need the final turn summary:
```ts
const response = await session.send("Summarize the latest forecast.");
const result = await response.result();
console.log(result.status);
console.log(result.message);
console.log(result.events.length);
```
This consumes the stream until the current turn boundary:
- `session.waiting`
- `session.completed`
- `session.failed`
## Stream events live
Use `for await...of` when you want to render progress:
```ts
const response = await session.send("Draft a plan and show your work.");
for await (const event of response) {
if (event.type === "message.appended") {
process.stdout.write(event.data.messageDelta);
}
if (event.type === "message.completed" && event.data.finishReason !== "tool-calls") {
console.log("\nfinal:", event.data.message);
}
}
```
`message.appended` and `reasoning.appended` are incremental delta events. Their completed forms, `message.completed` and `reasoning.completed`, are the compatibility path for clients that don't render deltas.
## Handle event types
Import event types from `eve/client` when you want exhaustiveness or helpers:
```ts
import type { HandleMessageStreamEvent } from "eve/client";
import { isCurrentTurnBoundaryEvent } from "eve/client";
function handleEvent(event: HandleMessageStreamEvent) {
if (isCurrentTurnBoundaryEvent(event)) {
console.log("turn settled:", event.type);
}
}
```
The most common UI events are:
| Event | Use |
| -------------------- | ---------------------------------------------------------------- |
| `message.received` | Confirm the user message landed. |
| `reasoning.appended` | Render reasoning deltas when the model provides them. |
| `message.appended` | Render assistant text deltas. |
| `actions.requested` | Show tool calls requested by the model. |
| `action.result` | Show tool call results. |
| `input.requested` | Pause the UI for approval or a question answer. |
| `result.completed` | Read structured output from an [output schema](./output-schema). |
| `session.waiting` | Enable the composer for the next turn. |
| `session.completed` | Mark the conversation terminal. |
| `session.failed` | Mark the conversation failed. |
For the complete event table, see [Sessions, runs & streaming](../../concepts/sessions-runs-and-streaming).
## Reconnection
The client reconnects after transient stream disconnects. It resumes from the number of events already consumed in the current session:
```ts
const client = new Client({
host: "https://agent.example.com",
maxReconnectAttempts: 5,
});
```
`maxReconnectAttempts` is per turn. The default is `3`.
## Open a stream manually
Use `session.stream()` when you already have a session cursor and only need to attach to the existing stream:
```ts
const session = client.session({
continuationToken: "eve:6c8b1f2e-3d4a-4b9c-8e21-9f0a1b2c3d4e",
sessionId: "wrun_01ARYZ6S41TSV4RRFFQ69G5FAV",
streamIndex: 10,
});
for await (const event of session.stream()) {
console.log(event.type);
}
```
Pass `startIndex` to override the stored cursor:
```ts
for await (const event of session.stream({ startIndex: 0 })) {
console.log(event.type);
}
```
`stream()` throws if the session has no `sessionId`, because there's no stream to attach to before the first send.
## Abort a request
Pass an `AbortSignal` to cancel the POST or stream. Arm the timeout before awaiting `send()` so it covers the POST as well as the stream:
```ts
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 10_000);
const response = await session.send({
message: "Run a long analysis.",
signal: controller.signal,
});
for await (const event of response) {
console.log(event.type);
}
clearTimeout(timeout);
```
Once a response is aborted, create a new send for the next turn. Don't reuse the same `MessageResponse`.
## What to read next
- [Messages](./messages): the send APIs that create streams
- [Continuations](./continuations): how stream cursors are persisted
- [Output schema](./output-schema): consume `result.completed`