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

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

## 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 one-turn context for the next model call. Strings become user-role context messages, arrays of strings become multiple context messages, and objects are JSON-serialized into one context message. 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.

## 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();
```

`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
