Vercel Eve

Connect a Vercel Eve agent to OpenUI and render streamed responses and tool calls as live generative UI.

OpenUI can render output from a Vercel Eve agent without replacing Eve's session protocol. This example uses Eve's built-in HTTP channel for delivery and resumable streaming, translates its events to AG-UI, and passes that stream to OpenUI's <FullScreen> chat.

The agent receives the OpenUI component-library prompt when a session starts, so its responses use OpenUI Lang and render as cards, tables, charts, forms, and other interactive components.

View source on GitHub →

How it connects

PieceFileRole
OpenUI chatsrc/app/page.tsxRenders <FullScreen> with agUIAdapter() and the built-in OpenUI component library.
Session bridgesrc/eve-chat.tsDelivers turns through Eve's HTTP API, follows its resumable event stream, and returns AG-UI SSE to OpenUI.
Event adaptersrc/eve-stream.tsMaps Eve text, tool-call, and failure events to AG-UI events.
Agent instructionsagent/instructions/openui.tsAdds the generated OpenUI system prompt when each Eve session starts.

The Next.js app and Eve development server start together through withEve(). The browser uses Eve's same-origin session endpoints directly, so the integration does not need a custom backend route or CORS configuration. OpenUI threads retain Eve's session ID, continuation token, and stream cursor, preserving multi-turn context and resumable delivery.

Connecting OpenUI

The page creates Eve-backed chat callbacks and passes them to <FullScreen>. OpenUI's AG-UI adapter consumes the SSE response produced by processMessage:

import { agUIAdapter } from "@openuidev/react-headless";
import { FullScreen } from "@openuidev/react-ui";
import { openuiChatLibrary } from "@openuidev/react-ui/genui-lib";
import { createEveChatProps } from "../eve-chat";

const chatProps = createEveChatProps();

<FullScreen
  {...chatProps}
  streamProtocol={agUIAdapter()}
  componentLibrary={openuiChatLibrary}
  agentName="Eve + OpenUI"
/>;

createEveChatProps() also provides OpenUI's thread callbacks. The example stores thread metadata, transcripts, Eve session IDs, continuation tokens, and stream cursors in localStorage, so each OpenUI thread resumes the corresponding Eve conversation.

Teaching Eve OpenUI Lang

Eve loads instructions from agent/instructions. A dynamic instruction adds the component library's generated prompt once when a session starts:

import { openuiChatLibrary, openuiChatPromptOptions } from "@openuidev/react-ui/genui-lib";
import { defineDynamic, defineInstructions } from "eve/instructions";

export default defineDynamic({
  events: {
    "session.started": () =>
      defineInstructions({
        markdown: openuiChatLibrary.prompt(openuiChatPromptOptions),
      }),
  },
});

This keeps the model's instructions synchronized with the exact component library used by the renderer.

The session bridge

The browser talks to the same-origin Eve endpoints installed by withEve():

POST /eve/v1/session
POST /eve/v1/session/:id
GET  /eve/v1/session/:id/stream?startIndex=N

The first turn creates a session. Follow-up turns send the saved continuation token, and the stream request resumes from the saved event index. Completed sessions clear the cursor; waiting and failed sessions remain resumable.

const delivered = await fetch(
  state.sessionId ? `/eve/v1/session/${state.sessionId}` : "/eve/v1/session",
  {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: JSON.stringify({ message, continuationToken: state.continuationToken }),
  },
);

const streamed = await fetch(`/eve/v1/session/${sessionId}/stream?startIndex=${state.streamIndex}`);

Tool calls and streamed UI

Eve emits typed session events. The adapter in src/eve-stream.ts converts the events OpenUI needs:

actions.requested  -> TOOL_CALL_START / TOOL_CALL_ARGS / TOOL_CALL_END
message.appended   -> TEXT_MESSAGE_CONTENT
message.completed  -> TEXT_MESSAGE_CONTENT (non-streaming fallback)
turn.failed        -> RUN_ERROR
session.failed     -> RUN_ERROR

Tool calls and text share one assistant message ID. OpenUI renders tool activity in its behind-the-scenes section and the final OpenUI Lang response in the conversation.

The included get_current_time tool is a minimal example. Additional Eve tools automatically surface through the same AG-UI tool-call mapping.

Thread persistence

src/thread-store.ts keeps OpenUI thread metadata and transcripts in browser localStorage. src/eve-chat.ts stores the Eve session ID, continuation token, and stream index under the same thread ID. Reopening a thread therefore restores both the visible transcript and its server-side Eve conversation.

Authentication and security

The demo channel uses anonymous authentication for local development. Replace none() in agent/channels/eve.ts with an authenticated Eve channel before exposing the application.

Project layout

examples/harnesses/vercel-eve/
|- agent/agent.ts                    # Eve model and build configuration
|- agent/channels/eve.ts             # Eve HTTP session channel
|- agent/instructions/openui.ts      # Generated OpenUI Lang instructions
|- agent/tools/get_current_time.ts   # Example Eve tool
|- src/app/page.tsx                  # OpenUI FullScreen chat
|- src/eve-chat.ts                   # Session transport and persistence
|- src/eve-stream.ts                 # Eve-to-AG-UI event mapping
|- src/thread-store.ts               # Browser thread and transcript storage
|- next.config.ts                    # Installs Eve with withEve()

Run the example

Eve 0.11 requires Node.js 24. From the repository root, follow these steps:

# 1. Install workspace dependencies.
pnpm install

# 2. Enter the example.
cd examples/harnesses/vercel-eve

# 3. Configure an OpenAI-compatible model provider.
cp .env.example .env
# Edit .env and set LLM_API_KEY.

# 4. Start Next.js and the embedded Eve development server.
pnpm dev

Open http://localhost:3000 and start a conversation. OPENAI_API_KEY, OPENAI_MODEL, and OPENAI_BASE_URL are accepted as aliases for the LLM_* variables.

The repository scripts also expose Eve directly when you need to build or run the agent separately:

pnpm eve:build
pnpm eve:start

On this page