Messages and Streaming

eve client applications interact with an agent one turn at a time. A turn starts when ClientSession.send() posts user input to the runtime, and it settles when the session stream reaches a current-turn boundary such as session.waiting, session.completed, or session.failed. This page explains how to choose between aggregated results and live event iteration, how to render streamed assistant output, and how to treat pauses, reconnection, and event handling as part of the same client contract.

Sources: docs/guides/client/streaming.mdx

Purpose and Scope

The client streaming guide is written for application code that needs to display agent progress, not just call an agent as a request-response endpoint. The important distinction is that send() returns a MessageResponse, and that response represents both the accepted turn and the event stream for that turn. If the UI only needs a final answer, it can aggregate the stream with result(). If the UI needs token-like progress, tool-call visibility, approvals, or structured output updates, it should iterate the response as an async iterable.

A message is the user-facing input for one turn. In the simplest case, it is a string passed to send(). In richer cases, official client docs describe a full turn payload with fields such as message and clientContext; that context is one-turn context for the next model call rather than durable chat history. Streaming is the delivery mechanism for everything the runtime emits while that turn is being processed, including user-message acknowledgement, reasoning deltas, assistant text deltas, tool activity, human-input requests, structured results, and terminal session state.

Sources: docs/guides/client/streaming.mdx

Relevant Source Files

  • docs/guides/client/streaming.mdx — Defines the public client streaming behavior: ClientSession.send(), MessageResponse.result(), async iteration with for await...of, common stream event types, authorization pauses, and reconnection settings.

Core Primitives

ClientSession.send() is the entry point for sending a turn. The guide states that every send posts the turn and then reads the session's NDJSON event stream. NDJSON means newline-delimited JSON: each line is one JSON event, so clients can process progress incrementally instead of waiting for the full session to finish. The returned MessageResponse is intentionally dual-purpose. It can be consumed as an aggregate promise-like result through result(), or it can be consumed live with async iteration.

MessageResponse.result() is the reducer path. It consumes stream events until the current turn has reached a boundary, then returns a summary that client code can use for simple workflows. The source guide identifies the turn boundaries as session.waiting, session.completed, and session.failed. In practical terms, use this mode for command-line scripts, server-side jobs, tests, or UI actions where only final status, final message text, and the event collection matter.

Async iteration is the rendering path. The response can be used in for await...of, which lets the application update its interface as each event arrives. The streaming guide highlights message.appended for assistant text deltas and message.completed for finalized text blocks. It also calls out reasoning.appended and reasoning.completed, which matter for clients that choose to show model reasoning when available. Deltas support progressive rendering; completed events are the compatibility path for renderers that prefer whole blocks.

Sources: docs/guides/client/streaming.mdx

Sending and Aggregating a Turn

Use result() when a feature behaves like a normal request-response interaction. The stream is still consumed internally, but the caller does not need to manage individual events. The guide's example sends a forecast summary request, awaits the result, and then reads the status, message, and number of observed events. This pattern is also a good default for unit tests around application behavior because it waits until the current turn is settled before making assertions.

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);

Aggregation should not be confused with starting a new session. The same send() call starts or resumes the durable session according to the ClientSession state, and result() only decides how the resulting stream is consumed. When the turn ends in session.waiting, application code can enable the composer and send another message on the same conversation. When the turn is completed or failed, the UI should treat the current conversation state according to the runtime's terminal status rather than blindly allowing follow-up input.

Sources: docs/guides/client/streaming.mdx

Rendering Stream Updates Live

Use live streaming when the user needs to see progress. The simplest renderer listens for message.appended and writes event.data.messageDelta, which represents the newly streamed assistant text. The guide also demonstrates using message.completed with a finishReason check so clients can render a final assistant block without treating intermediate tool-call completions as final user-visible answers. That distinction is important for agents that call tools before producing final text.

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);
  }
}

A production UI usually reduces stream events into its own chat state. message.received can confirm that the user's message landed. reasoning.appended can update a reasoning panel when that surface is enabled. actions.requested can display planned tool calls before execution, and action.result can add tool results to an activity timeline. input.requested should pause the normal composer and present approval or question UI. result.completed is the event to read when a turn requested a structured output schema. The session events then drive composer and thread lifecycle state.

Sources: docs/guides/client/streaming.mdx

Event Types and Exhaustiveness

The guide recommends importing event types from eve/client when a handler needs exhaustiveness or helper functions. The public type named in the docs is HandleMessageStreamEvent, and the helper shown is isCurrentTurnBoundaryEvent. This combination is useful when a renderer, reducer, or logging layer wants a single switch over event types while still recognizing when the current turn has settled.

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 common event table in the guide is a compact contract for UI behavior. session.waiting means the conversation can accept another user turn. session.completed marks the conversation terminal. session.failed marks failure. These are not just labels for logs; they should directly influence whether the composer is enabled, whether retry UI is shown, and whether the current saved session state can be reused. Treating all end-like events the same is a common source of confusing chat behavior.

Sources: docs/guides/client/streaming.mdx

Authorization Pauses and Reconnection

authorization.required is specifically not the same as session.waiting. The guide says it means a connection needs OAuth or another authorization challenge before the parked turn can continue. A chat UI should render the authorization prompt, disable ordinary text input for that session, and persist the event with the rest of the chat history. The absence of session.waiting after this event should not be interpreted as a finished conversation, because the callback or a structured decline is expected to resume the same eve session.

Reconnection is also part of the client model rather than an application afterthought. The guide states that the client reconnects after transient stream disconnects and resumes from the number of events already consumed in the current session. That means an app should treat the stream cursor as remote session position, not as its own database row index. Persist the events needed for rendering separately from the cursor used for reconnecting, and rehydrate both when a user refreshes during a long-running or authorization-paused turn.

Sources: docs/guides/client/streaming.mdx

Compact Reference

Primitive or eventClient behavior
ClientSession.send()Posts one turn and begins reading the session NDJSON stream.
MessageResponse.result()Aggregates stream events until session.waiting, session.completed, or session.failed.
for await (const event of response)Streams events live for progressive rendering and activity timelines.
message.appendedRender assistant text deltas.
reasoning.appendedRender reasoning deltas when exposed by the model and UI.
message.completedRender finalized assistant text for clients that do not use deltas.
actions.requestedShow requested tool calls before execution.
action.resultShow tool results.
input.requestedPause for approval or question answering.
result.completedRead structured output from an output schema.
authorization.requiredPause for OAuth or another authorization challenge; do not treat as normal waiting.
session.waitingRe-enable the composer for the next turn.
session.completedMark the conversation terminal.
session.failedMark the conversation failed.

Next Steps

Start with result() while building the first integration, then switch to async iteration when the product needs progress rendering, tool-call timelines, approvals, or structured output display. Keep event rendering, session persistence, and reconnect cursors separate in your application model: the event log is what the user sees, while the session state and stream index are how the client resumes the runtime conversation. For adjacent concepts, read the client overview, continuations and output schema, and the sessions, runs, and streaming concept page.