---
title: Messages
description: Send text, full turn payloads, client context, attachments, and HITL responses with eve/client.
---

# Messages



Create a session with its first turn, then use the returned `ClientSession` for follow-ups. Each handle targets one durable session ID.

## Send text

Pass a string to `send()` for plain text:

```ts
import { Client } from "eve/client";

const client = new Client({ host: "http://127.0.0.1:2000" });
const { session, response } = await client.sessions.create({
  message: "What is the weather in Brooklyn?",
});

// Metadata is available as soon as the POST succeeds.
console.log(response.sessionId);

const result = await response.result();
console.log(result.status, result.message);
```

`response.result()` consumes the event stream and returns a `MessageResult`:

| Field       | Meaning                                                                        |
| ----------- | ------------------------------------------------------------------------------ |
| `message`   | Final assistant text for the turn, when one completed.                         |
| `status`    | `"waiting"`, `"completed"`, or `"failed"`.                                     |
| `events`    | All stream events observed during the turn.                                    |
| `sessionId` | Session ID for streaming and inspection.                                       |
| `data`      | Structured output when the turn requested an [output schema](./output-schema). |

When the stream includes `session.failed`, the turn returns `status: "failed"` rather than throwing. Transport and route errors throw `ClientError`.

`session.send()` retries `409 session_not_active` three times when a durable run has been accepted
but its command inbox is still starting. The retries wait 250 ms, 500 ms, and 1 second. Other
errors, `session.respond()`, control methods, and raw HTTP requests do not use this retry. An unknown
session still throws `ClientError` immediately, while a terminal session throws after the final
attempt. The client never creates a replacement session.

## Send a full turn payload

Pass the full payload to `create()` for the first turn or `send()` for a follow-up:

```ts
const { session, response } = await client.sessions.create({
  message: "What should I do on this screen?",
  clientContext: {
    route: "/billing",
    plan: "pro",
    seatsUsed: 4,
  },
});

await response.result();
```

`clientContext` is ephemeral context for the current turn. Strings become user-role context messages, arrays of strings become multiple context messages, and objects are JSON-serialized into one context message. The context remains available to every model call in the turn, then disappears before the next turn. It isn't persisted to durable session history and doesn't dispatch a turn by itself.

## Send attachments

`send()` accepts AI SDK `UserContent`, so a message can mix text and file parts:

```ts
const response = await session.send([
  { type: "text", text: "Summarize this report." },
  {
    type: "file",
    data: reportDataUrl,
    mediaType: "application/pdf",
    filename: "report.pdf",
  },
]);

await response.result();
```

For local files, read the file and send a base64 `data:` URL:

```ts
import { readFile } from "node:fs/promises";

const bytes = await readFile("report.pdf");
const reportDataUrl = `data:application/pdf;base64,${bytes.toString("base64")}`;

const response = await session.send([
  { type: "text", text: "Summarize this report." },
  {
    type: "file",
    data: reportDataUrl,
    mediaType: "application/pdf",
    filename: "report.pdf",
  },
]);

await response.result();
```

The stream confirms the turn with `message.received`. Its `data.message` remains the flattened
summary for compatibility, and `data.parts` contains structured text and file metadata for clients
that render attachments. File parts never include raw bytes or internal sandbox paths. See [Inbound
attachments](../../sandbox#inbound-attachments) for how eve stages byte-backed files in the session
sandbox and prepares them for each model call.

## Answer human input requests

Tools can pause for approval or ask the user a question. The stream emits `input.requested` with one or more requests. Reply through the same session with `inputResponses`:

```ts
import type { InputRequest } from "eve/client";

let pendingRequests: readonly InputRequest[] = [];

const response = await session.send("Run the deployment checks.");

for await (const event of response) {
  if (event.type === "input.requested") {
    pendingRequests = event.data.requests;
  }
}

const resumed = await session.respond(
  pendingRequests.map((request) => ({
    requestId: request.requestId,
    optionId: "approve",
  })),
);

await resumed.result();
```

After eve accepts the reply, the durable stream emits `input.resolved`. Its `resolutions` array includes each request's `requestId`, `kind`, terminal `outcome`, and the accepted `response` when the client provided one. Persist this authoritative event instead of relying on the submitting client's optimistic state when rebuilding message history.

`send(message, options)` and `respond(inputResponses, options)` are separate operations. Put `clientContext`, `outputSchema`, headers, or stream options in the second argument to either method.

## Single-use responses

`MessageResponse` is single-use. Either aggregate it:

```ts
const result = await response.result();
```

Or stream it:

```ts
for await (const event of response) {
  console.log(event.type);
}
```

Don't do both on the same response. Once the stream is consumed, the `ClientSession` advances its cursor for the next turn.

## What to read next

* [Continuations](./continuations): how the session cursor advances
* [Streaming](./streaming): handle events live instead of using `result()`
* [Tools](../../tools): configure approvals and question prompts


---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)