Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/vercel/eve/llms.txt

Use this file to discover all available pages before exploring further.

The eve/client package is the typed TypeScript client for eve’s default HTTP API. Use it from scripts, server-to-server integrations, tests, evals, backend jobs, or custom UIs that want the session protocol without hand-writing the POST and NDJSON stream loop. For browser chat UIs, start with useEveAgent. For wire-level details, read the Sessions, runs & streaming reference. The client sits between those two: lower level than the frontend hooks, higher level than raw HTTP.

Create a client

A Client binds one host, auth policy, header policy, and stream reconnection budget:
import { Client } from "eve/client";

const client = new Client({
  host: "http://127.0.0.1:3000",
});
host is the origin where the eve routes are mounted. In a same-origin browser integration this is often ""; scripts and backend services name the full URL.

Check health and inspect

Use health() when a script needs to fail early before creating a session:
const health = await client.health();
console.log(health.status, health.workflowId);
Non-2xx responses throw ClientError, which carries the HTTP status and response body. Use info() to inspect a development agent. The client parses and validates the complete response before returning it:
const info = await client.info();
console.log(info.agent.name, info.agent.model.id);

Authentication

Pass auth when the eve channel route requires credentials. Bearer values and Basic auth passwords can be strings or async functions — functions run before every HTTP call, including stream reconnects:
const client = new Client({
  host: "https://agent.example.com",
  auth: {
    bearer: async () => await getAccessToken(),
  },
});
Use headers for route-specific credentials such as bypass tokens or tenant hints:
const client = new Client({
  host: "https://agent.example.com",
  headers: async () => ({
    "x-vercel-protection-bypass": await getBypassToken(),
  }),
  redirect: "manual",
});
Set redirect to "manual" or "error" on credential-bearing clients so fetch cannot forward custom authorization headers to another origin.

Create a session

Create a ClientSession for each conversation:
const session = client.session();
A client can own many sessions at once. Each session tracks its own sessionId, continuationToken, and stream cursor independently:
const alice = client.session();
const bob = client.session();

const aliceResponse = await alice.send("Summarize account A.");
await aliceResponse.result();

const bobResponse = await bob.send("Summarize account B.");
await bobResponse.result();

Send a message

Pass a string to send() for plain text:
const client = new Client({ host: "http://127.0.0.1:3000" });
const session = client.session();

const response = await session.send("What is the weather in Brooklyn?");

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

const result = await response.result();
console.log(result.status, result.message);
response.result() consumes the event stream and returns a MessageResult:
FieldMeaning
messageFinal assistant text for the turn, when one completed.
status"waiting", "completed", or "failed".
eventsAll stream events observed during the turn.
sessionIdSession ID for streaming and inspection.
dataStructured output when the turn requested an 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

Use send() with an object when you need more than plain text — clientContext, attachments, or HITL responses:
const response = await session.send({
  message: "What should I do on this screen?",
  clientContext: {
    route: "/billing",
    plan: "pro",
    seatsUsed: 4,
  },
});

await response.result();
clientContext is one-turn ephemeral context for the next model call. It isn’t persisted to durable session history and doesn’t dispatch a turn by itself.

Send file attachments

send() accepts AI SDK UserContent, so a message can mix text and file parts. For local files, read the bytes and build a base64 data: URL:
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({
  message: [
    { type: "text", text: "Summarize this report." },
    {
      type: "file",
      data: reportDataUrl,
      mediaType: "application/pdf",
      filename: "report.pdf",
    },
  ],
});

await response.result();

Stream events live

Use for await...of when you want to render progress as the model replies:
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 are the compatibility path for clients that don’t render deltas.
MessageResponse is single-use — either aggregate it with result() or iterate it with for await...of. Don’t do both on the same response.

Handle event types

Import event types from eve/client for exhaustiveness or helpers:
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:
EventUse
message.receivedConfirm the user message landed.
reasoning.appendedRender reasoning deltas when the model provides them.
message.appendedRender assistant text deltas.
actions.requestedShow tool calls requested by the model.
action.resultShow tool call results.
input.requestedPause the UI for approval or a question answer.
result.completedRead structured output from an output schema.
session.waitingEnable the composer for the next turn.
session.completedMark the conversation terminal.
session.failedMark the conversation failed.

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:
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.send({
  inputResponses: pendingRequests.map((request) => ({
    requestId: request.requestId,
    optionId: "approve",
  })),
});

await resumed.result();
You can send message, inputResponses, and clientContext together when the resumed turn needs both a human answer and follow-up text.

Attach to an existing stream

Use session.stream() when you already have a session cursor and only need to attach to the existing stream without sending a new turn:
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 position:
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 both:
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.

Reconnection

The client reconnects after transient stream disconnects, resuming from the number of events already consumed:
const client = new Client({
  host: "https://agent.example.com",
  maxReconnectAttempts: 5,
});
maxReconnectAttempts is per turn. The default is 3.

Per-request headers

Attach headers to an individual turn rather than every request on the client:
const response = await session.send({
  message: "Run the check.",
  headers: { "x-request-id": requestId },
});

await response.result();

Frontend Integration

Browser chat UIs with useEveAgent for React, Vue, and Svelte

Evals

Drive the agent through sessions and assert on results

Channels

The HTTP API this client calls

Deployment

Deploy your agent so the client can reach it

Build docs developers (and LLMs) love