Chat application using the AG-UI protocol for agent-user interaction with DeepCitation citation verification.
- Single SSE stream — LLM tokens and verification results flow through one connection
- AG-UI protocol events —
RUN_STARTED,TEXT_MESSAGE_*,STATE_DELTA,STATE_SNAPSHOT,RUN_FINISHED - Real-time streaming — Tokens appear as they arrive from the LLM
- Citation verification — Automatically verifies citations after streaming completes
- Visual proof — Proof images and hosted proof URLs for each citation
Browser Next.js API Route
────── ─────────────────
useAgentChat() ──POST──────▶ /api/agent/route.ts
│ │
│◀── RUN_STARTED ─────────────│
│◀── TEXT_MESSAGE_START ──────│
│◀── TEXT_MESSAGE_CONTENT ────│ (repeated — streamed LLM tokens)
│◀── TEXT_MESSAGE_END ────────│
│◀── STATE_DELTA ─────────────│ ({ verificationStatus: "verifying" })
│◀── STATE_SNAPSHOT ──────────│ ({ citations, verifications, summary })
│◀── RUN_FINISHED ────────────│
The key difference from the nextjs-ai-sdk example: chat streaming and citation verification happen over a single SSE stream using AG-UI protocol events, instead of two separate HTTP requests (/api/chat + /api/verify).
cd examples/agui-chat
npm install
cp .env.example .env
# Add your API keys to .env
npm run dev
# Open http://localhost:3000src/
├── app/
│ ├── api/
│ │ ├── agent/route.ts # AG-UI SSE endpoint (core)
│ │ └── upload/route.ts # File upload endpoint
│ ├── globals.css # Tailwind + citation styles
│ ├── layout.tsx # Root layout
│ ├── page.tsx # Main chat page
│ ├── error.tsx # Error boundary
│ ├── global-error.tsx # Global error boundary
│ └── not-found.tsx # 404 page
├── components/
│ ├── ChatMessage.tsx # Message display with inline citations
│ ├── FileUpload.tsx # Document upload button
│ └── VerificationPanel.tsx # Side panel with verification results
├── hooks/
│ └── useAgentChat.ts # React hook wrapping @ag-ui/client
├── lib/
│ └── agui-events.ts # AG-UI event builder helpers
└── utils/
└── citationDrawerAdapter.ts # Drawer item conversion
User uploads a PDF via /api/upload. The server calls dc.prepareAttachments() and returns a FileDataPart plus deepTextPages for that upload; the client stores those pages in a deepTextPagesByAttachmentId map keyed by attachmentId.
When the user sends a message, useAgentChat opens a POST request to /api/agent. The server:
- Wraps prompts with
wrapCitationPrompt()using the document text - Streams OpenAI
gpt-5-miniresponse asTEXT_MESSAGE_CONTENTevents - After streaming completes, extracts citations with
getAllCitationsFromLlmOutput() - Verifies against the source document with
dc.verifyAttachment() - Sends verification results as a
STATE_SNAPSHOTevent
The connection stays open until both LLM streaming and verification finish.
ChatMessage parses <cite /> tags from the LLM output and renders them as interactive CitationComponent elements with verification status indicators.
| Route | Method | Description |
|---|---|---|
/api/agent |
POST | AG-UI SSE endpoint — streams LLM + verification |
/api/upload |
POST | Upload documents for verification |
| Aspect | nextjs-ai-sdk | agui-chat |
|---|---|---|
| Protocol | AI SDK streaming + REST | AG-UI SSE protocol |
| Endpoints | 3 (/chat, /verify, /upload) |
2 (/agent, /upload) |
| Verification | Separate client-initiated request | Server-side, same stream |
| Client hook | useChat (AI SDK) |
useAgentChat (custom) |
| Models | OpenAI + Gemini | OpenAI only |
| State sync | Manual (isLoading detection) | AG-UI STATE_DELTA/SNAPSHOT |
- Multi-document verification: Only the first uploaded document's citations are verified. Citations from additional documents will show as "not found"
- Bundle size:
@ag-ui/clientdepends on RxJS, which adds to the client bundle - Long sessions: Each message's verification state is ~20KB. After 50+ messages, state can reach ~1MB
- No auto-reconnect: If the SSE connection drops mid-stream, the hook surfaces an error. Use the retry button to resend