Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions examples/ts-react-copilotkit/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
node_modules
.DS_Store
dist
dist-ssr
*.local
.env
.nitro
.tanstack
.output
.vinxi
114 changes: 114 additions & 0 deletions examples/ts-react-copilotkit/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# TanStack AI — AG-UI Protocol Example

This example demonstrates how **TanStack AI** implements the open [AG-UI protocol](https://docs.ag-ui.com) standard for agent-user interaction.

## What is AG-UI?

AG-UI (Agent-User Interaction) is an open protocol that standardizes how AI agents communicate with user interfaces. TanStack AI is AG-UI compliant, enabling interoperability with other AG-UI compatible clients.

## Architecture

```
┌─────────────────────────────────────────────────┐
│ Client │
│ │
│ ┌──────────────────┐ │
│ │ TanStack AI │ │
│ │ useChat() hook │ │
│ └────────┬─────────┘ │
│ │ │
│ │ AG-UI Protocol │
│ │ (SSE / Events) │
│ │ │
└───────────┼──────────────────────────────────────┘
┌───────────────────────────────────────────────────┐
│ Server │
│ │
│ ┌─────────────────────────────────────────────┐ │
│ │ TanStack AI Server │ │
│ │ │ │
│ │ chat() → AG-UI Events → SSE Response │ │
│ │ │ │
│ │ Adapters: OpenAI, Anthropic, Gemini, etc. │ │
│ └─────────────────────────────────────────────┘ │
└───────────────────────────────────────────────────┘
```

## What This Example Shows

1. **TanStack AI Server**: The `/api/chat` endpoint uses TanStack AI's `chat()` function with OpenAI adapter to process messages and stream AG-UI events via Server-Sent Events (SSE).

2. **TanStack AI Client**: Uses `useChat` hook from `@tanstack/ai-react` with `fetchServerSentEvents` to consume the AG-UI stream.

3. **AG-UI Protocol**: The communication protocol that enables standardized agent-server interaction, supporting interoperability with other AG-UI compliant systems.

## AG-UI Events

The server streams events following the AG-UI protocol:

| Event | Description |
|-------|------------|
| `RUN_STARTED` | Signals the beginning of an AI run |
| `TEXT_MESSAGE_START` | Marks the start of a text message |
| `TEXT_MESSAGE_CONTENT` | Streams text content chunks |
| `TEXT_MESSAGE_END` | Marks the end of a text message |
| `TOOL_CALL_START` | Initiates a tool call |
| `TOOL_CALL_ARGS` | Streams tool call arguments |
| `TOOL_CALL_END` | Completes a tool call |
| `RUN_FINISHED` | Signals completion of the run |

## Getting Started

### Prerequisites

- Node.js 18+
- pnpm
- An OpenAI API key

### Setup

1. Create a `.env` file in this directory:

```bash
OPENAI_API_KEY=your-openai-api-key
```

2. Install dependencies from the monorepo root:

```bash
cd ../..
pnpm install
```

3. Run the example:

```bash
cd examples/ts-react-copilotkit
pnpm dev
```

4. Open [http://localhost:3001](http://localhost:3001) in your browser.

### Switching Between Clients

Use the tabs at the top of the page to switch between:
- **🔥 TanStack AI Client** — Uses `@tanstack/ai-react`'s `useChat` hook
- **🪁 CopilotKit Client** — Uses CopilotKit's `<CopilotChat>` component

Both connect to the same TanStack AI server endpoint.
Comment on lines +94 to +100
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

README describes tab-switching UI that doesn't exist in the code.

The README references switching between "🔥 TanStack AI Client" and "🪁 CopilotKit Client" tabs, but src/routes/index.tsx only renders TanStackAIChat — there's no CopilotKit client component or tab UI implemented. Either update the README to match the current implementation or add the missing CopilotKit client tab.

🤖 Prompt for AI Agents
In `@examples/ts-react-copilotkit/README.md` around lines 94 - 100, The README
mentions a tabbed UI switching between "TanStack AI Client" and "CopilotKit
Client" but the app only renders TanStackAIChat; either remove/update the README
to reflect the single-client app or implement the missing tab/UI and component:
add a tab bar in src/routes/index.tsx that toggles rendering between
TanStackAIChat and a CopilotChat component (create or import CopilotChat if
missing), wire the tab state to conditionally render the chosen component, and
ensure any imports/exports and documentation in README.md are updated to match
the implemented behavior.


## Key Files

| File | Description |
|------|-------------|
| `src/routes/api.chat.ts` | Server endpoint using TanStack AI's `chat()` function |
| `src/routes/index.tsx` | Client page with both TanStack AI and CopilotKit UIs |
| `src/routes/__root.tsx` | Root layout |

## Learn More

- [TanStack AI Documentation](https://tanstack.com/ai)
- [CopilotKit Documentation](https://docs.copilotkit.ai)
- [AG-UI Protocol](https://docs.ag-ui.com)
34 changes: 34 additions & 0 deletions examples/ts-react-copilotkit/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"name": "ts-react-copilotkit",
"private": true,
"type": "module",
"scripts": {
"dev": "vite dev --port 3001",
"build": "vite build",
"serve": "vite preview",
"test": "exit 0"
},
"dependencies": {
"@ag-ui/client": "^0.0.45",
"@tailwindcss/vite": "^4.1.18",
"@tanstack/ai": "workspace:*",
"@tanstack/ai-client": "workspace:*",
"@tanstack/ai-openai": "workspace:*",
"@tanstack/ai-react": "workspace:*",
"@tanstack/nitro-v2-vite-plugin": "^1.154.7",
"@tanstack/react-router": "^1.158.4",
"@tanstack/react-start": "^1.159.0",
"react": "^19.2.3",
"react-dom": "^19.2.3",
"tailwindcss": "^4.1.18",
"zod": "^4.2.0"
},
"devDependencies": {
"@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.1.2",
"typescript": "5.9.3",
"vite": "^7.2.7",
"vite-tsconfig-paths": "^5.1.4"
}
}
113 changes: 113 additions & 0 deletions examples/ts-react-copilotkit/src/routeTree.gen.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/* eslint-disable */

// @ts-nocheck

// noinspection JSUnusedGlobalSymbols

// This file was automatically generated by TanStack Router.
// You should NOT make any changes in this file as it will be overwritten.
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.

import { Route as rootRouteImport } from './routes/__root'
import { Route as IndexRouteImport } from './routes/index'
import { Route as ApiChatRouteImport } from './routes/api.chat'
import { Route as ApiChatInfoRouteImport } from './routes/api.chat.info'

const IndexRoute = IndexRouteImport.update({
id: '/',
path: '/',
getParentRoute: () => rootRouteImport,
} as any)
const ApiChatRoute = ApiChatRouteImport.update({
id: '/api/chat',
path: '/api/chat',
getParentRoute: () => rootRouteImport,
} as any)
const ApiChatInfoRoute = ApiChatInfoRouteImport.update({
id: '/info',
path: '/info',
getParentRoute: () => ApiChatRoute,
} as any)

export interface FileRoutesByFullPath {
'/': typeof IndexRoute
'/api/chat': typeof ApiChatRouteWithChildren
'/api/chat/info': typeof ApiChatInfoRoute
}
export interface FileRoutesByTo {
'/': typeof IndexRoute
'/api/chat': typeof ApiChatRouteWithChildren
'/api/chat/info': typeof ApiChatInfoRoute
}
export interface FileRoutesById {
__root__: typeof rootRouteImport
'/': typeof IndexRoute
'/api/chat': typeof ApiChatRouteWithChildren
'/api/chat/info': typeof ApiChatInfoRoute
}
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
fullPaths: '/' | '/api/chat' | '/api/chat/info'
fileRoutesByTo: FileRoutesByTo
to: '/' | '/api/chat' | '/api/chat/info'
id: '__root__' | '/' | '/api/chat' | '/api/chat/info'
fileRoutesById: FileRoutesById
}
export interface RootRouteChildren {
IndexRoute: typeof IndexRoute
ApiChatRoute: typeof ApiChatRouteWithChildren
}

declare module '@tanstack/react-router' {
interface FileRoutesByPath {
'/': {
id: '/'
path: '/'
fullPath: '/'
preLoaderRoute: typeof IndexRouteImport
parentRoute: typeof rootRouteImport
}
'/api/chat': {
id: '/api/chat'
path: '/api/chat'
fullPath: '/api/chat'
preLoaderRoute: typeof ApiChatRouteImport
parentRoute: typeof rootRouteImport
}
'/api/chat/info': {
id: '/api/chat/info'
path: '/info'
fullPath: '/api/chat/info'
preLoaderRoute: typeof ApiChatInfoRouteImport
parentRoute: typeof ApiChatRoute
}
}
}

interface ApiChatRouteChildren {
ApiChatInfoRoute: typeof ApiChatInfoRoute
}

const ApiChatRouteChildren: ApiChatRouteChildren = {
ApiChatInfoRoute: ApiChatInfoRoute,
}

const ApiChatRouteWithChildren =
ApiChatRoute._addFileChildren(ApiChatRouteChildren)

const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
ApiChatRoute: ApiChatRouteWithChildren,
}
export const routeTree = rootRouteImport
._addFileChildren(rootRouteChildren)
._addFileTypes<FileRouteTypes>()

import type { getRouter } from './router.tsx'
import type { createStart } from '@tanstack/react-start'
declare module '@tanstack/react-start' {
interface Register {
ssr: true
router: Awaited<ReturnType<typeof getRouter>>
}
}
11 changes: 11 additions & 0 deletions examples/ts-react-copilotkit/src/router.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { createRouter } from '@tanstack/react-router'

import { routeTree } from './routeTree.gen'

export const getRouter = () => {
return createRouter({
routeTree,
scrollRestoration: true,
defaultPreloadStaleTime: 0,
})
}
41 changes: 41 additions & 0 deletions examples/ts-react-copilotkit/src/routes/__root.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { HeadContent, Scripts, createRootRoute } from '@tanstack/react-router'
import appCss from '../styles.css?url'

export const Route = createRootRoute({
head: () => ({
meta: [
{
charSet: 'utf-8',
},
{
name: 'viewport',
content: 'width=device-width, initial-scale=1',
},
{
title: 'TanStack AI + CopilotKit — AG-UI Integration',
},
],
links: [
{
rel: 'stylesheet',
href: appCss,
},
],
}),

shellComponent: RootDocument,
})

function RootDocument({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<head>
<HeadContent />
</head>
<body>
{children}
<Scripts />
</body>
</html>
)
}
57 changes: 57 additions & 0 deletions examples/ts-react-copilotkit/src/routes/api.chat.info.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { createFileRoute } from '@tanstack/react-router'

/**
* Runtime info endpoint for CopilotKit/AG-UI compatibility.
*
* CopilotKit's HttpAgent client may fetch runtime information
* about available agents and their capabilities.
*
* Supports both GET (for simple info queries) and POST (for detailed requests).
*/
export const Route = createFileRoute('/api/chat/info')({
server: {
handlers: {
GET: async () => {
const runtimeInfo = {
agents: [
{
id: 'tanstack-ai',
name: 'tanstack-ai',
description: 'TanStack AI agent connected via AG-UI protocol',
capabilities: ['chat', 'sse', 'ag-ui-protocol'],
},
],
provider: 'tanstack-ai',
version: '1.0.0',
}

return new Response(JSON.stringify(runtimeInfo), {
status: 200,
headers: { 'Content-Type': 'application/json' },
})
},

POST: async ({ request }) => {
// Some AG-UI clients may POST to /info for discovery
// Return the same runtime information
const runtimeInfo = {
agents: [
{
id: 'tanstack-ai',
name: 'tanstack-ai',
description: 'TanStack AI agent connected via AG-UI protocol',
capabilities: ['chat', 'sse', 'ag-ui-protocol'],
},
],
provider: 'tanstack-ai',
version: '1.0.0',
}

return new Response(JSON.stringify(runtimeInfo), {
status: 200,
headers: { 'Content-Type': 'application/json' },
})
},
},
},
})
Loading