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.
How it connects
| Piece | File | Role |
|---|---|---|
| OpenUI chat | src/app/page.tsx | Renders <FullScreen> with agUIAdapter() and the built-in OpenUI component library. |
| Session bridge | src/eve-chat.ts | Delivers turns through Eve's HTTP API, follows its resumable event stream, and returns AG-UI SSE to OpenUI. |
| Event adapter | src/eve-stream.ts | Maps Eve text, tool-call, and failure events to AG-UI events. |
| Agent instructions | agent/instructions/openui.ts | Adds 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=NThe 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_ERRORTool 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 devOpen 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