Imported from agentuity/sdk-explorer (
.agents/agentuity/sdk/web/AGENTS.md). Install upstream withnpx skills add agentuity/sdk-explorer --skill web. Copyright stays with the author.
Web Folder Guide
This folder contains your React-based web application that communicates with your Agentuity agents.
Generated Types
The src/generated/ folder contains auto-generated TypeScript files:
routes.ts- Route registry with type-safe API, WebSocket, and SSE route definitionsregistry.ts- Agent registry with input/output types
Important: Never edit files in src/generated/ - they are overwritten on every build.
Import generated types in your components:
// Routes are typed automatically via module augmentation
import { useAPI } from '@agentuity/react';
// The route 'GET /api/users' is fully typed
const { data } = useAPI('GET /api/users');
Directory Structure
Required files:
- App.tsx (required) - Main React application component
- frontend.tsx (required) - Frontend entry point with client-side rendering
- index.html (required) - HTML template
- public/ (optional) - Static assets (images, CSS, JS files)
Example structure:
src/web/
├── App.tsx
├── frontend.tsx
├── index.html
└── public/
├── styles.css
├── logo.svg
└── script.js
Creating the Web App
App.tsx - Main Component
import { AgentuityProvider, useAPI } from '@agentuity/react';
import { useState } from 'react';
function HelloForm() {
const [name, setName] = useState('World');
const { invoke, isLoading, data: greeting } = useAPI('POST /api/hello');
return (
<div>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
disabled={isLoading}
/>
<button
onClick={() => invoke({ name })}
disabled={isLoading}
>
{isLoading ? 'Running...' : 'Say Hello'}
</button>
<div>{greeting ?? 'Waiting for response'}</div>
</div>
);
}
export function App() {
return (
<AgentuityProvider>
<div style={{ fontFamily: 'sans-serif', padding: '2rem' }}>
<h1>Welcome to Agentuity</h1>
<HelloForm />
</div>
</AgentuityProvider>
);
}
frontend.tsx - Entry Point
import { createRoot } from 'react-dom/client';
import { App } from './App';
const root = document.getElementById('root');
if (!root) throw new Error('Root element not found');
createRoot(root).render(<App />);
index.html - HTML Template
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>My Agentuity App</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/web/frontend.tsx"></script>
</body>
</html>
React Hooks
All hooks from @agentuity/react must be used within an AgentuityProvider. Always use these hooks instead of raw fetch() calls - they provide type safety, automatic error handling, and integration with the Agentuity platform.
useAPI - Type-Safe API Calls
The primary hook for making HTTP requests. Use this instead of fetch().
import { useAPI } from '@agentuity/react';
function MyComponent() {
// GET requests auto-execute and return refetch
const { data, isLoading, error, refetch } = useAPI('GET /api/users');
// POST/PUT/DELETE return invoke for manual execution
const { invoke, data: result, isLoading: saving } = useAPI('POST /api/users');
const handleCreate = async () => {
// Input is fully typed from route schema!
await invoke({ name: 'Alice', email: 'alice@example.com' });
};
return (
<div>
<button onClick={handleCreate} disabled={saving}>
{saving ? 'Creating...' : 'Create User'}
</button>
{result && <p>Created: {result.name}</p>}
</div>
);
}
useAPI Return Values:
| Property | Type | Description |
|---|---|---|
data |
T | undefined |
Response data (typed from route schema) |
error |
Error | null |
Error if request failed |
isLoading |
boolean |
True during initial load |
isFetching |
boolean |
True during any fetch (including refetch) |
isSuccess |
boolean |
True if last request succeeded |
isError |
boolean |
True if last request failed |
invoke |
(input?) => Promise<T> |
Manual trigger (POST/PUT/DELETE) |
refetch |
() => Promise<void> |
Refetch data (GET) |
reset |
() => void |
Reset state to initial |
useAPI Options
// GET with query parameters and caching
const { data } = useAPI({
route: 'GET /api/search',
query: { q: 'react', limit: '10' },
staleTime: 5000, // Cache for 5 seconds
refetchInterval: 10000, // Auto-refetch every 10 seconds
enabled: true, // Set to false to disable auto-fetch
});
// POST with callbacks
const { invoke } = useAPI({
route: 'POST /api/users',
onSuccess: (data) => console.log('Created:', data),
onError: (error) => console.error('Failed:', error),
});
// Streaming responses with onChunk
const { invoke } = useAPI({
route: 'POST /api/stream',
onChunk: (chunk) => console.log('Received chunk:', chunk),
delimiter: '\n', // Split stream by newlines (default)
});
// Custom headers
const { data } = useAPI({
route: 'GET /api/protected',
headers: { 'X-Custom-Header': 'value' },
});
useWebsocket - WebSocket Connection
For bidirectional real-time communication. Automatically handles reconnection.
import { useWebsocket } from '@agentuity/react';
function ChatComponent() {
const { isConnected, data, send, messages, clearMessages, error, reset } = useWebsocket('/api/chat');
return (
<div>
<p>Status: {isConnected ? 'Connected' : 'Disconnected'}</p>
<button onClick={() => send({ message: 'Hello' })}>Send</button>
<div>
{messages.map((msg, i) => (
<p key={i}>{JSON.stringify(msg)}</p>
))}
</div>
<button onClick={clearMessages}>Clear</button>
</div>
);
}
useWebsocket Return Values:
| Property | Type | Description |
|---|---|---|
isConnected |
boolean |
True when WebSocket is connected |
data |
T | undefined |
Most recent message received |
messages |
T[] |
Array of all received messages |
send |
(data) => void |
Send a message (typed from route schema) |
clearMessages |
() => void |
Clear the messages array |
close |
() => void |
Close the connection |
error |
Error | null |
Error if connection failed |
isError |
boolean |
True if there's an error |
reset |
() => void |
Reset state and reconnect |
readyState |
number |
WebSocket ready state |
useEventStream - Server-Sent Events
For server-to-client streaming (one-way). Use when server pushes updates to client.
import { useEventStream } from '@agentuity/react';
function NotificationsComponent() {
const { isConnected, data, error, close, reset } = useEventStream('/api/notifications');
return (
<div>
<p>Connected: {isConnected ? 'Yes' : 'No'}</p>
{error && <p>Error: {error.message}</p>}
<p>Latest: {JSON.stringify(data)}</p>
<button onClick={close}>Disconnect</button>
</div>
);
}
useEventStream Return Values:
| Property | Type | Description |
|---|---|---|
isConnected |
boolean |
True when EventSource is connected |
data |
T | undefined |
Most recent event data |
error |
Error | null |
Error if connection failed |
isError |
boolean |
True if there's an error |
close |
() => void |
Close the connection |
reset |
() => void |
Reset state and reconnect |
readyState |
number |
EventSource ready state |
useAgentuity - Access Context
Access the Agentuity context for base URL and configuration.
import { useAgentuity } from '@agentuity/react';
function MyComponent() {
const { baseUrl } = useAgentuity();
return <p>API Base: {baseUrl}</p>;
}
useAuth - Authentication State
Access and manage authentication state.
import { useAuth } from '@agentuity/react';
function AuthStatus() {
const { isAuthenticated, authHeader, setAuthHeader, authLoading } = useAuth();
const handleLogin = async (token: string) => {
setAuthHeader?.(`Bearer ${token}`);
};
const handleLogout = () => {
setAuthHeader?.(null);
};
if (authLoading) return <p>Loading...</p>;
return (
<div>
{isAuthenticated ? (
<button onClick={handleLogout}>Logout</button>
) : (
<button onClick={() => handleLogin('my-token')}>Login</button>
)}
</div>
);
}
useAuth Return Values:
| Property | Type | Description |
|---|---|---|
isAuthenticated |
boolean |
True if user has auth token and not loading |
authHeader |
string | null |
Current auth header (e.g., "Bearer ...") |
setAuthHeader |
(token) => void |
Set auth header (null to clear) |
authLoading |
boolean |
True during auth state changes |
setAuthLoading |
(loading) => void |
Set auth loading state |
Complete Example
import { AgentuityProvider, useAPI, useWebsocket } from '@agentuity/react';
import { useEffect, useState } from 'react';
function Dashboard() {
const [count, setCount] = useState(0);
const { invoke, data: agentResult } = useAPI('POST /api/process');
const { isConnected, send, data: wsMessage } = useWebsocket('/api/live');
useEffect(() => {
if (isConnected) {
const interval = setInterval(() => {
send({ ping: Date.now() });
}, 1000);
return () => clearInterval(interval);
}
}, [isConnected, send]);
return (
<div style={{ fontFamily: 'sans-serif', padding: '2rem' }}>
<h1>My Agentuity App</h1>
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(c => c + 1)}>
Increment
</button>
</div>
<div>
<button onClick={() => invoke({ name: 'Jeff', age: 30 })}>
Call API
</button>
<p>{JSON.stringify(agentResult)}</p>
</div>
<div>
<strong>WebSocket:</strong>
{isConnected ? JSON.stringify(wsMessage) : 'Not connected'}
</div>
</div>
);
}
export function App() {
return (
<AgentuityProvider>
<Dashboard />
</AgentuityProvider>
);
}
Static Assets
Place static files in the public/ folder:
src/web/public/
├── logo.svg
├── styles.css
└── script.js
Reference them in your HTML or components:
<!-- In index.html -->
<link rel="stylesheet" href="/public/styles.css" />
<script src="/public/script.js"></script>
// In React components
<img src="/public/logo.svg" alt="Logo" />
Styling
Inline Styles
<div style={{ backgroundColor: '#000', color: '#fff', padding: '1rem' }}>
Styled content
</div>
CSS Files
Create public/styles.css:
body {
background-color: #09090b;
color: #fff;
font-family: sans-serif;
}
Import in index.html:
<link rel="stylesheet" href="/public/styles.css" />
Style Tag in Component
<div>
<button className="glow-btn">Click me</button>
<style>{`
.glow-btn {
background: linear-gradient(to right, #155e75, #3b82f6);
border: none;
padding: 0.75rem 1.5rem;
color: white;
cursor: pointer;
}
`}</style>
</div>
RPC-Style API Client
For non-React contexts (like utility functions or event handlers), use createClient:
import { createClient } from '@agentuity/react';
// Create a typed client (uses global baseUrl and auth from AgentuityProvider)
const api = createClient();
// Type-safe RPC-style calls - routes become nested objects
// Route 'GET /api/users' becomes api.users.get()
// Route 'POST /api/users' becomes api.users.post()
// Route 'GET /api/users/:id' becomes api.users.id.get({ id: '123' })
async function fetchData() {
const users = await api.users.get();
const newUser = await api.users.post({ name: 'Alice', email: 'alice@example.com' });
const user = await api.users.id.get({ id: '123' });
return { users, newUser, user };
}
When to use createClient vs useAPI:
| Use Case | Recommendation |
|---|---|
| React component rendering | useAPI hook |
| Event handlers | Either works |
| Utility functions | createClient |
| Non-React code | createClient |
| Need loading/error state | useAPI hook |
| Need caching/refetch | useAPI hook |
Best Practices
- Wrap your app with AgentuityProvider for hooks to work
- Always use
useAPIinstead offetch()for type safety and error handling - Use useAPI for type-safe HTTP requests (GET, POST, PUT, DELETE)
- Use useWebsocket for bidirectional real-time communication
- Use useEventStream for server-to-client streaming
- Use useAuth for authentication state management
- Handle loading and error states in UI
- Place reusable components in separate files
- Keep static assets in the public/ folder
Rules
- App.tsx must export a function named
App - frontend.tsx must render the
Appcomponent to#root - index.html must have a
<div id="root"></div> - Routes are typed via module augmentation from
src/generated/routes.ts - The web app is served at
/by default - Static files in
public/are served at/public/* - Module script tag:
<script type="module" src="/web/frontend.tsx"></script> - Never use raw
fetch()calls - always useuseAPIorcreateClient