Files
composio-skills-reference/composio-sdk/rules/tr-building-chat-ui.md
sohamganatra b8b711dff6 Add Composio SDK skill with rules and agent config
Adds composio-sdk/ with SKILL.md, AGENTS.md, and 18 rule files
covering Tool Router, direct execution, triggers, and auth patterns.

Source: composiohq/skills

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-05 22:54:21 -08:00

348 lines
9.3 KiB
Markdown

---
title: Building Chat UIs with Tool Router
impact: HIGH
description: Best practices for building chat applications with toolkit selection, connection management, and session handling
tags: [tool-router, chat-ui, vercel-ai-sdk, toolkit-selection, authentication, session]
---
# Building Chat UIs with Tool Router
Build chat applications with Tool Router using **Vercel AI SDK**, create **sessions per message** with dynamic configuration, and provide **toolkit selection** and **connection management** UI.
## Recommended: Vercel AI SDK
- Native streaming support
- React hooks for chat interfaces
- Built-in UI components
- Excellent DX with Tool Router
## ❌ Incorrect - Sharing Sessions Without Config
```typescript
// DON'T: Reuse sessions without proper configuration
const globalSession = await composio.create('default', {
toolkits: ['gmail'] // Hard-coded toolkits
});
app.post('/api/chat', async (req, res) => {
// ❌ No user isolation
// ❌ No per-message configuration
// ❌ Can't change toolkits dynamically
const tools = await globalSession.tools();
});
```
## ✅ Correct - Session Per Message
```typescript
// DO: Create sessions per message with proper config
import { Composio } from '@composio/core';
import { VercelProvider } from '@composio/vercel';
import { streamText } from 'ai';
import { openai } from '@ai-sdk/openai';
const composio = new Composio({ provider: new VercelProvider() });
app.post('/api/chat', async (req, res) => {
const { userId, message, selectedToolkits } = req.body;
// Create new session for this message
const session = await composio.create(userId, {
toolkits: selectedToolkits, // User-selected toolkits
manageConnections: true
});
const tools = await session.tools();
const stream = await streamText({
model: openai('gpt-4o'),
messages: [{ role: 'user', content: message }],
tools,
maxSteps: 10
});
return stream.toDataStreamResponse();
});
```
## Toolkit Selection UI
### List All Available Toolkits
Create a session **without toolkit filters** to show all available toolkits:
```typescript
// API endpoint to list all toolkits
app.post('/api/toolkits', async (req, res) => {
const { userId } = req.body;
// No toolkits parameter = all toolkits available
const session = await composio.create(userId);
const toolkits = await session.toolkits();
res.json(toolkits.map(tk => ({
slug: tk.slug,
name: tk.name,
description: tk.description,
logo: tk.logo,
isConnected: tk.connectedAccounts.length > 0
})));
});
```
### React Component
```typescript
export function ToolkitSelector({ userId, onSelect }: Props) {
const [toolkits, setToolkits] = useState<Toolkit[]>([]);
const [selected, setSelected] = useState<string[]>([]);
useEffect(() => {
fetch('/api/toolkits', {
method: 'POST',
body: JSON.stringify({ userId })
}).then(res => res.json()).then(setToolkits);
}, [userId]);
return (
<div className="toolkit-grid">
{toolkits.map(tk => (
<div
key={tk.slug}
className={selected.includes(tk.slug) ? 'selected' : ''}
onClick={() => setSelected(prev =>
prev.includes(tk.slug) ? prev.filter(s => s !== tk.slug) : [...prev, tk.slug]
)}
>
<img src={tk.logo} alt={tk.name} />
<h3>{tk.name}</h3>
{tk.isConnected && <span> Connected</span>}
</div>
))}
<button onClick={() => onSelect(selected)}>Use Selected</button>
</div>
);
}
```
## Connection Management UI
### Authorize Toolkits
```typescript
// API endpoint to start connection flow
app.post('/api/connect', async (req, res) => {
const { userId, toolkitSlug } = req.body;
const session = await composio.create(userId, {
toolkits: [toolkitSlug]
});
const auth = await session.authorize({
toolkit: toolkitSlug,
redirectUrl: `${process.env.APP_URL}/auth/callback`
});
res.json({ redirectUrl: auth.redirectUrl });
});
```
### React Component
```typescript
export function ConnectedAccounts({ userId }: Props) {
const [toolkits, setToolkits] = useState<Toolkit[]>([]);
const handleConnect = async (slug: string) => {
const res = await fetch('/api/connect', {
method: 'POST',
body: JSON.stringify({ userId, toolkitSlug: slug })
});
const { redirectUrl } = await res.json();
window.location.href = redirectUrl;
};
return (
<div>
{toolkits.map(tk => (
<div key={tk.slug}>
<h3>{tk.name}</h3>
{tk.isConnected ? (
<button onClick={() => handleDisconnect(tk.slug)}>Disconnect</button>
) : (
<button onClick={() => handleConnect(tk.slug)}>Connect</button>
)}
</div>
))}
</div>
);
}
```
## Connected Account Sharing
**Connected accounts are shared between sessions** (tied to user ID and auth configs, not individual sessions).
```typescript
// Both sessions use the same Gmail connected account
const session1 = await composio.create('user_123', { toolkits: ['gmail'] });
const session2 = await composio.create('user_123', { toolkits: ['gmail', 'slack'] });
// ✅ Connected accounts shared across sessions
// ✅ No need to reconnect for each session
```
### Override Connected Accounts
```typescript
// Override which connected account to use
const session = await composio.create('user_123', {
toolkits: ['gmail'],
connectedAccounts: {
gmail: 'conn_specific_account_id' // Use specific account
}
});
```
### Override Auth Config
```typescript
// Override which auth config to use
const session = await composio.create('user_123', {
toolkits: ['gmail'],
authConfig: {
gmail: 'auth_config_custom_id' // Use custom auth config
}
});
```
## Complete Chat Application
```typescript
// app/api/chat/route.ts
import { Composio } from '@composio/core';
import { VercelProvider } from '@composio/vercel';
import { streamText } from 'ai';
import { openai } from '@ai-sdk/openai';
const composio = new Composio({ provider: new VercelProvider() });
export async function POST(req: Request) {
const { userId, messages, selectedToolkits } = await req.json();
const session = await composio.create(userId, {
toolkits: selectedToolkits,
manageConnections: true
});
const tools = await session.tools();
const result = await streamText({
model: openai('gpt-4o'),
messages,
tools,
maxSteps: 10
});
return result.toDataStreamResponse();
}
```
```typescript
// app/page.tsx - Chat UI
'use client';
import { useChat } from 'ai/react';
import { useState } from 'react';
export default function ChatPage() {
const [selectedToolkits, setSelectedToolkits] = useState(['gmail']);
const { messages, input, handleInputChange, handleSubmit } = useChat({
api: '/api/chat',
body: { userId: 'user_123', selectedToolkits }
});
return (
<div>
<ToolkitSelector
userId="user_123"
selected={selectedToolkits}
onSelect={setSelectedToolkits}
/>
<div className="messages">
{messages.map(m => (
<div key={m.id} className={m.role}>{m.content}</div>
))}
</div>
<form onSubmit={handleSubmit}>
<input value={input} onChange={handleInputChange} />
<button type="submit">Send</button>
</form>
</div>
);
}
```
## Manual Tool Operations (Advanced)
For custom workflows, you can manually fetch and execute tools instead of using sessions.
### Manual Tool Fetching
```typescript
// Fetch raw tool metadata
const tools = await composio.tools.getRawComposioTools({
toolkits: ['gmail', 'slack'],
important: true
});
```
### Manual Tool Execution
```typescript
// Execute tools directly
const result = await composio.tools.execute('GMAIL_SEND_EMAIL', {
userId: 'user_123',
arguments: { to: 'test@example.com', subject: 'Hello' },
version: '15082025_00' // Version REQUIRED for manual execution
});
if (!result.successful) {
console.error('Failed:', result.error);
}
```
### When to Use Manual Approach
| Use Case | Recommended Approach |
|----------|---------------------|
| Chat UIs, agents, streaming | ✅ `session.tools()` |
| Custom workflows, catalogs | ✅ Manual fetch/execute |
**Reference:** See [Fetching Tools](./app-fetch-tools.md) and [Tool Execution](./app-execute-tools.md) for detailed manual operation guides.
## Best Practices
1. **Create Sessions Per Message** - Fresh session with config for each interaction
2. **Let Users Select Toolkits** - Dynamic toolkit configuration via UI
3. **Show Connection Status** - Display which toolkits are connected
4. **Handle Authorization** - Use `session.authorize()` for auth flows
5. **Enable Connection Management** - Set `manageConnections: true`
## Key Principles
1. **Vercel AI SDK** - Best framework for chat UIs
2. **Session per message** - Fresh sessions with config
3. **No toolkit filter** - List all by creating session without toolkits
4. **Shared connections** - Connected accounts shared across sessions
5. **Override when needed** - Use `connectedAccounts` or `authConfig` for special cases
## Reference
- [Vercel AI SDK](https://sdk.vercel.ai)
- [Tool Router Sessions](https://docs.composio.dev/sdk/typescript/api/tool-router#creating-sessions)
- [Session Authorization](https://docs.composio.dev/sdk/typescript/api/tool-router#authorization)
- [Fetching Tools](./app-fetch-tools.md)
- [Tool Execution](./app-execute-tools.md)
- [Tool Versions](./app-tool-versions.md)