Integrate with Next.js and React
Keep the provider and Runner on the server, then stream normalized UI events to a browser-safe chat client.
The browser package never runs providers or tools. A React client sends user input to your route; the route owns identity, credentials, policy, tool execution, and durable sessions.
Install
bun add @zhivex-ai/react @zhivex-ai/sdk @zhivex-ai/openai react react-dom
Create the server route
In app/api/chat/route.ts:
import {
Agent,
createPostgresSessionService,
createRunner,
fromUIMessage,
toUIRunnerStreamResponse,
type AgentApprovalResponse,
type UIMessage
} from "@zhivex-ai/sdk";
import { createOpenAI } from "@zhivex-ai/openai";
export const runtime = "nodejs";
const openai = createOpenAI({ apiKey: process.env.OPENAI_API_KEY });
const runner = createRunner({
appName: "support-chat",
agent: new Agent({
model: openai("gpt-6-astra"),
instructions: "Answer clearly and briefly."
}),
sessionService: createPostgresSessionService({ client: postgresClient })
});
export async function POST(request: Request) {
const body = await request.json() as {
message?: UIMessage;
sessionId?: string;
approvals?: AgentApprovalResponse[];
};
const userId = await resolveCurrentUserId(request);
if (!body.message && !body.approvals?.length) {
return Response.json({ error: "Missing message or approval." }, { status: 400 });
}
const stream = runner.stream({
userId,
sessionId: body.sessionId,
messages: body.message ? [fromUIMessage(body.message)] : undefined,
approvals: body.approvals,
abortSignal: request.signal
});
return toUIRunnerStreamResponse(stream);
}
postgresClient and resolveCurrentUserId() belong to the application. Map the authenticated identity to the SDK call; never trust a browser-supplied user or tenant id as authorization.
Add the React client
Import the default stylesheet once from app/layout.tsx:
import "@zhivex-ai/react/styles.css";
Then create a client component:
"use client";
import { ZhivexChat, useZhivexChat } from "@zhivex-ai/react";
export function SupportChat() {
const chat = useZhivexChat({ endpoint: "/api/chat" });
return (
<ZhivexChat
controller={chat}
header={<strong>Support assistant</strong>}
starterPrompts={[
"Summarize the latest updates",
"Help me plan a rollout"
]}
/>
);
}
The default transport sends the latest user message, current session id, and approval decisions. Runner + SessionService remains the source of truth for durable history.
Configure the ready-made React experience
The ready-made chat now consumes the controller’s run activity, multimodal send API, status, copy, and optional retry capabilities. It also supports explicit themes and compact density without replacing the default stylesheet:
<ZhivexChat
controller={chat}
density="compact"
theme="dark"
formatError={() => "The assistant could not complete this request."}
composerProps={{
accept: "image/*,application/pdf",
maxAttachments: 3,
maxAttachmentBytes: 2 * 1024 * 1024
}}
messageListProps={{
approvalCardProps: {
reasonRequired: true,
description: "This action sends data outside the current workspace."
}
}}
/>
- Attachments can be selected, pasted, or dropped; keep count and byte limits narrow or upload larger files through an application-owned flow.
- Transport and server details are hidden from users by default. Return only reviewed text from
formatError; reserveshowErrorDetailsfor trusted diagnostics. themeacceptssystem,light, ordark; every primitive exposes a stabledata-slotfor application-owned styling.- Retry remains opt-in because replay can duplicate durable history. Enable it only when the endpoint implements idempotent regeneration.
React 0.3 changed the ready-made visual system, and those contracts remain available in 0.4. Recheck screenshots, CSS-variable overrides, focus behavior, compact layouts, and mobile attachment flows before rollout.
Keep an AI SDK UI v7 client
React 0.4 adds the Beta @zhivex-ai/react/compat entrypoint for applications that deliberately keep the AI SDK UI reducer and components:
bun add @zhivex-ai/react ai@^7 @ai-sdk/react@^4
import { useChat } from "@ai-sdk/react";
import { createAISDKUIChatTransport } from "@zhivex-ai/react/compat";
const chat = useChat({
transport: createAISDKUIChatTransport({ endpoint: "/api/chat/stream" })
});
The compatibility transport converts message, tool, and reasoning parts at the boundary; rejects redirects; propagates abort and approvals; and applies bounded SSE parsing. UI-only metadata stays in a compatibility sidecar and is not forwarded to providers. Keep this entrypoint opt-in and verify its versioned part matrix when upgrading either ecosystem.
Model-aware media and agent views
With @zhivex-ai/react 0.6.0, pass the server-selected model’s serializable inputCapabilities to useZhivexChat. An explicit inputMediaTypes list overrides broad flags; an empty list disables attachments. The composer checks selection, paste, drop and uploads, but the server must validate the inputs again.
const chat = useZhivexChat({
endpoint: "/api/chat",
inputCapabilities: {
vision: true,
audioInput: true,
files: true,
inputMediaTypes: ["image/*", "audio/*", "video/*"],
},
});
Use capabilities returned by your own backend for the selected model; the example describes a model that accepts these media types. Video file parts render without autoplay, and remote media remains opt-in under the application URL policy.
streamAgent() emits additive agent-run-update summaries. ZhivexChat shows AgentRunsPanel when summaries exist; use showAgentRuns={false} to hide it. The reducer keeps at most 200 summaries in chat.state.runs, with child hierarchy via parentRunId. Summaries omit prompts, tool arguments and full state; query an authenticated run store for historical inspection. Resumed approvals complete the original tool card and keep the final reply as an assistant message.
Optional realtime voice
HTTP chat and browser voice use separate transports. Import useZhivexRealtime, createWebSocketRealtimeTransport and createBrowserRealtimeAudio from @zhivex-ai/react/realtime. Host createRealtimeRelay from @zhivex-ai/react/realtime-server behind an authenticated WebSocket upgrade with Origin validation.
Connect from a user action, then explicitly start or stop the microphone. The default driver captures mono PCM16 at 16 kHz and plays 24 kHz output; agree on format and rates with the relay. AudioWorklet, microphone permission and a secure context are required. Provider keys and tools stay on the server. Reconnect is explicit rather than automatic.
See realtime media and interruption and the versioned React example.
Production checklist
- authenticate and rate-limit before provider work;
- use Postgres instead of local files on serverless infrastructure;
- validate request sizes and tool inputs;
- pass
request.signalinto the runner and downstream I/O; - redact sensitive payloads before trace export;
- apply a narrow allowlist before rendering remote media.
See observability and evaluations and the production guide before exposing the route to real users.