React Hooks
All hooks are provided by @ainative/react-sdk and require the AINativeProvider to be present in the component tree.
npm install @ainative/react-sdk
Requires: React 18+
Setup — AINativeProvider
Wrap your application (or the subtree that uses AINative hooks) with AINativeProvider. This configures the API key and base URL for all child hooks.
import { AINativeProvider } from '@ainative/react-sdk';
export function App({ children }: { children: React.ReactNode }) {
return (
<AINativeProvider
config={{
apiKey: process.env.NEXT_PUBLIC_AINATIVE_API_KEY!,
// baseUrl defaults to 'https://api.ainative.studio/api/v1'
}}
>
{children}
</AINativeProvider>
);
}
AINativeProvider props
| Name | Type | Default | Description |
|---|---|---|---|
config | AINativeConfig | required | API configuration |
config.apiKey | string | required | Your AINative JWT token or API key |
config.baseUrl | string | 'https://api.ainative.studio/api/v1' | Override the API base URL |
children | ReactNode | required | Component subtree |
useAINative
Access the underlying API client configuration from any component inside the provider. Useful for building custom hooks or performing raw fetch calls with the configured credentials.
Signature
function useAINative(): AINativeClient
Return type
interface AINativeClient {
config: AINativeConfig; // { apiKey, baseUrl? }
baseUrl: string; // Resolved base URL
}
Example
import { useAINative } from '@ainative/react-sdk';
export function DebugPanel() {
const client = useAINative();
return (
<pre className="text-xs">
{JSON.stringify({ baseUrl: client.baseUrl }, null, 2)}
</pre>
);
}
useChat
Send chat completion requests and manage conversation state. All messages in the conversation are stored in the hook's local state; call sendMessage with the full message array (including history) to maintain context across turns.
Signature
function useChat(options?: UseChatOptions): {
messages: Message[];
isLoading: boolean;
error: AINativeError | null;
response: ChatCompletionResponse | null;
sendMessage: (messages: Message[]) => Promise<ChatCompletionResponse | null>;
reset: () => void;
}
Options
| Name | Type | Default | Description |
|---|---|---|---|
model | string | — | Preferred model identifier (e.g. 'meta-llama/llama-3.3-70b-instruct') |
temperature | number | — | Sampling temperature (0–2) |
max_tokens | number | — | Maximum tokens to generate |
onError | (error: AINativeError) => void | — | Callback invoked on request failure |
onSuccess | (response: ChatCompletionResponse) => void | — | Callback invoked on successful completion |
Return values
| Name | Type | Description |
|---|---|---|
messages | Message[] | Current conversation messages (user + assistant turns) |
isLoading | boolean | true while a request is in flight |
error | AINativeError | null | Last error, or null if none |
response | ChatCompletionResponse | null | Raw response from the last successful request |
sendMessage | (messages: Message[]) => Promise<ChatCompletionResponse | null> | Send a new completion request. Pass the full message array including history |
reset | () => void | Clear all messages and reset state to the initial empty state |
Example
import { useState } from 'react';
import { useChat } from '@ainative/react-sdk';
import type { Message } from '@ainative/react-sdk';
export function ChatBox() {
const { messages, sendMessage, isLoading, error, reset } = useChat({
model: 'meta-llama/llama-3.3-70b-instruct',
temperature: 0.7,
onError: (err) => console.error('Chat error:', err.message),
});
const [input, setInput] = useState('');
const handleSend = async () => {
if (!input.trim() || isLoading) return;
const newMessages: Message[] = [
...messages,
{ role: 'user', content: input },
];
setInput('');
await sendMessage(newMessages);
};
return (
<div>
<div className="messages">
{messages.map((msg, i) => (
<div key={i} className={`message ${msg.role}`}>
<strong>{msg.role}:</strong> {msg.content}
</div>
))}
</div>
{error && <p className="error">{error.message}</p>}
<div className="input-row">
<input
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleSend()}
disabled={isLoading}
placeholder="Ask anything…"
/>
<button onClick={handleSend} disabled={isLoading}>
{isLoading ? 'Sending…' : 'Send'}
</button>
<button onClick={reset}>Clear</button>
</div>
</div>
);
}
useAgent
Manage agent registrations — create, list, retrieve, and delete agent records via the AINative Agent API. Fetches the agent list automatically on mount.
Signature
function useAgent(): UseAgentReturn
Return type
interface UseAgentReturn {
agents: AgentRegistration[];
isLoading: boolean;
error: AINativeError | null;
create: (config: CreateAgentRequest) => Promise<AgentRegistration | null>;
get: (id: string) => Promise<AgentRegistration | null>;
list: () => Promise<AgentRegistration[]>;
remove: (id: string) => Promise<boolean>;
refetch: () => Promise<void>;
}
Key types
interface AgentRegistration {
id: string;
name: string;
agent_type: string;
model?: string;
capabilities: string[];
oversight_level: string;
status: string;
created_at: string;
}
interface CreateAgentRequest {
name: string;
agent_type: string;
model?: string;
capabilities: string[];
oversight_level?: string;
}
Methods
| Name | Signature | Description |
|---|---|---|
create | (config: CreateAgentRequest) => Promise<AgentRegistration | null> | Register a new agent. Adds it to the local list on success |
get | (id: string) => Promise<AgentRegistration | null> | Fetch a single agent by ID |
list | () => Promise<AgentRegistration[]> | Refresh the agent list from the API and update local state |
remove | (id: string) => Promise<boolean> | Delete an agent. Removes it from local state on success |
refetch | () => Promise<void> | Re-run the initial load (same as list but returns void) |
Example
import { useAgent } from '@ainative/react-sdk';
export function AgentManager() {
const { agents, isLoading, error, create, remove } = useAgent();
const handleCreate = async () => {
await create({
name: 'Summarizer',
agent_type: 'assistant',
capabilities: ['summarize', 'extract'],
oversight_level: 'medium',
});
};
if (isLoading) return <p>Loading agents…</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<div>
<button onClick={handleCreate}>Register Agent</button>
<ul>
{agents.map((agent) => (
<li key={agent.id}>
<strong>{agent.name}</strong> — {agent.agent_type}
<span className="status">{agent.status}</span>
<button onClick={() => remove(agent.id)}>Delete</button>
</li>
))}
</ul>
</div>
);
}
useCredits
Fetch and display a user's credit balance. Runs once on mount; call refetch to refresh.
Signature
function useCredits(): UseCreditsReturn
Return type
interface UseCreditsReturn {
balance: CreditBalance | null;
isLoading: boolean;
error: AINativeError | null;
refetch: () => Promise<void>;
}
interface CreditBalance {
total_credits: number;
used_credits: number;
remaining_credits: number;
plan: string;
period_start: string;
period_end: string | null;
usage_percentage: number;
}
Example
import { useCredits } from '@ainative/react-sdk';
export function CreditsWidget() {
const { balance, isLoading, error, refetch } = useCredits();
if (isLoading) return <span>Loading…</span>;
if (error) return <span>Failed to load credits</span>;
if (!balance) return null;
const pct = balance.usage_percentage.toFixed(0);
return (
<div className="credits-widget">
<p>
{balance.remaining_credits.toLocaleString()} credits remaining
({pct}% used)
</p>
<p className="plan">Plan: {balance.plan}</p>
<button onClick={refetch}>Refresh</button>
</div>
);
}
useMemory
Store, recall, and delete memories via the ZeroMemory API (/public/memory/v2). When an entityId is provided the hook auto-loads memories for that entity on mount.
Signature
function useMemory(entityId?: string): UseMemoryReturn
Parameters
| Name | Type | Description |
|---|---|---|
entityId | string (optional) | Scope all memory operations to this entity ID. When provided, memories are fetched on mount |
Return type
interface UseMemoryReturn {
memories: Memory[];
isLoading: boolean;
error: AINativeError | null;
remember: (content: string, options?: RememberOptions) => Promise<Memory | null>;
recall: (query: string, options?: RecallOptions) => Promise<Memory[]>;
forget: (memoryId: string) => Promise<boolean>;
refetch: () => Promise<void>;
}
Key types
interface Memory {
id: string;
content: string;
memory_type: string;
importance: number; // 0–1 importance score
tags: string[];
entity_id?: string;
metadata: Record<string, unknown>;
created_at: string;
score?: number; // Similarity score returned by recall
}
interface RememberOptions {
entity_id?: string; // Override the hook-level entityId
memory_type?: string; // e.g. 'episodic', 'semantic', 'procedural'
importance?: number; // 0–1
tags?: string[];
metadata?: Record<string, unknown>;
}
interface RecallOptions {
entity_id?: string; // Override the hook-level entityId
layer?: string; // Memory layer filter
limit?: number; // Maximum results to return
}
Methods
| Name | Signature | Description |
|---|---|---|
remember | (content: string, options?: RememberOptions) => Promise<Memory | null> | Store a new memory. Prepends to local list on success |
recall | (query: string, options?: RecallOptions) => Promise<Memory[]> | Semantic search over stored memories. Updates local list with results |
forget | (memoryId: string) => Promise<boolean> | Delete a memory by ID. Removes it from local list on success |
refetch | () => Promise<void> | Re-run the initial entity load (only active when entityId was provided) |
Example
import { useMemory } from '@ainative/react-sdk';
export function UserMemoryPanel({ userId }: { userId: string }) {
const { memories, remember, recall, forget, isLoading, error } = useMemory(userId);
const handleSave = () =>
remember('User prefers concise responses without preamble', {
memory_type: 'preference',
importance: 0.8,
tags: ['communication', 'style'],
});
const handleSearch = () =>
recall('communication preferences', { limit: 10 });
if (isLoading) return <p>Loading memories…</p>;
return (
<div>
<button onClick={handleSave}>Save preference</button>
<button onClick={handleSearch}>Search</button>
{error && <p className="error">{error.message}</p>}
<ul>
{memories.map((m) => (
<li key={m.id}>
{m.content}
{m.score !== undefined && (
<small> (score: {m.score.toFixed(3)})</small>
)}
<button onClick={() => forget(m.id)}>Forget</button>
</li>
))}
</ul>
</div>
);
}
useThread
Create and manage conversation threads. Fetches the thread list on mount. Supports full CRUD, message appending, semantic/keyword search, and thread forking.
Signature
function useThread(): UseThreadReturn
Return type
interface UseThreadReturn {
threads: Thread[];
isLoading: boolean;
error: AINativeError | null;
create: (title?: string, agentTypes?: string[]) => Promise<Thread | null>;
get: (threadId: string) => Promise<ThreadWithMessages | null>;
list: (opts?: { limit?: number; status?: string; search?: string }) => Promise<Thread[]>;
appendMessage: (
threadId: string,
message: { role: string; content: string }
) => Promise<ThreadMessage | null>;
search: (query: string, mode?: SearchMode) => Promise<Thread[]>;
fork: (threadId: string, fromMessageId: string, title?: string) => Promise<Thread | null>;
remove: (threadId: string) => Promise<boolean>;
refetch: () => Promise<void>;
}
Key types
type SearchMode = 'semantic' | 'keyword' | 'hybrid';
interface Thread {
id: string;
title: string | null;
agent_types: string[];
model: string | null;
status: string;
message_count: number;
last_message_at: string | null;
metadata: Record<string, unknown>;
created_at: string;
}
interface ThreadMessage {
id: string;
thread_id: string;
role: string;
content: string | null;
tool_calls: unknown[] | null;
tokens_used: number | null;
created_at: string;
}
interface ThreadWithMessages extends Thread {
messages: ThreadMessage[];
}
Methods
| Name | Signature | Description |
|---|---|---|
create | (title?, agentTypes?) => Promise<Thread | null> | Create a new thread. Prepends to local list |
get | (threadId) => Promise<ThreadWithMessages | null> | Fetch a thread with its full message history |
list | (opts?) => Promise<Thread[]> | Refresh thread list with optional filters |
appendMessage | (threadId, message) => Promise<ThreadMessage | null> | Add a message to a thread. Updates message_count in local state |
search | (query, mode?) => Promise<Thread[]> | Search threads. Defaults to 'semantic' mode |
fork | (threadId, fromMessageId, title?) => Promise<Thread | null> | Fork a thread from a specific message, creating a new branch |
remove | (threadId) => Promise<boolean> | Delete a thread and remove it from local state |
refetch | () => Promise<void> | Reload the thread list |
Example
import { useState } from 'react';
import { useThread } from '@ainative/react-sdk';
export function ThreadSidebar() {
const { threads, isLoading, error, create, get, remove, search } =
useThread();
const [query, setQuery] = useState('');
const handleNew = () => create('New conversation');
const handleSearch = async () => {
if (query.trim()) await search(query, 'hybrid');
};
const handleFetchMessages = async (threadId: string) => {
const full = await get(threadId);
if (full) console.log(full.messages);
};
if (isLoading) return <p>Loading threads…</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<aside>
<button onClick={handleNew}>New thread</button>
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search threads…"
onKeyDown={(e) => e.key === 'Enter' && handleSearch()}
/>
<ul>
{threads.map((t) => (
<li key={t.id}>
<button onClick={() => handleFetchMessages(t.id)}>
{t.title ?? 'Untitled'} ({t.message_count} msgs)
</button>
<button onClick={() => remove(t.id)}>Delete</button>
</li>
))}
</ul>
</aside>
);
}
useTask
Submit tasks to the AINative agent swarm and track their progress. Automatically polls the active task until it reaches a terminal state (completed, failed, or cancelled).
Signature
function useTask(options?: UseTaskOptions): UseTaskReturn
Options
| Name | Type | Default | Description |
|---|---|---|---|
pollInterval | number | 2000 | Polling interval in milliseconds while a task is active |
Return type
interface UseTaskReturn {
tasks: SwarmTask[];
status: SwarmTaskStatus | null;
result: Record<string, unknown> | null;
error: AINativeError | null;
isLoading: boolean;
submit: (
description: string,
agentTypes?: string[],
config?: Record<string, unknown>
) => Promise<SwarmTask | null>;
poll: (taskId: string) => Promise<SwarmTask | null>;
cancel: (taskId: string) => Promise<boolean>;
listTasks: (opts?: {
status?: string;
limit?: number;
offset?: number;
}) => Promise<SwarmTask[]>;
}
Key types
type SwarmTaskStatus = 'queued' | 'running' | 'completed' | 'failed' | 'cancelled';
interface SwarmTask {
task_id: string;
status: SwarmTaskStatus;
description: string;
agent_types: string[];
config: Record<string, unknown>;
result: Record<string, unknown> | null;
agents_used: string[];
created_at: string;
updated_at: string;
}
Methods
| Name | Signature | Description |
|---|---|---|
submit | (description, agentTypes?, config?) => Promise<SwarmTask | null> | Submit a task to the swarm and begin auto-polling |
poll | (taskId) => Promise<SwarmTask | null> | Manually poll a specific task for its current status |
cancel | (taskId) => Promise<boolean> | Cancel an active task and stop polling |
listTasks | (opts?) => Promise<SwarmTask[]> | Fetch the task list with optional status filter, limit, and offset |
Example
import { useTask } from '@ainative/react-sdk';
export function TaskRunner() {
const { submit, status, result, error, isLoading, cancel, tasks } = useTask({
pollInterval: 3000,
});
const handleRun = async () => {
const task = await submit(
'Analyze Q1 customer feedback and summarize key themes',
['analyst', 'summarizer'],
{ format: 'bullet_points', max_themes: 5 }
);
if (task) console.log('Task created:', task.task_id);
};
const handleCancel = () => {
const activeTask = tasks.find(
(t) => t.status === 'running' || t.status === 'queued'
);
if (activeTask) cancel(activeTask.task_id);
};
return (
<div>
<button onClick={handleRun} disabled={isLoading}>
Run Analysis
</button>
{isLoading && (
<button onClick={handleCancel}>Cancel</button>
)}
{status && (
<p>
Status: <strong>{status}</strong>
</p>
)}
{result && (
<pre>{JSON.stringify(result, null, 2)}</pre>
)}
{error && <p className="error">{error.message}</p>}
</div>
);
}
Next.js usage
All hooks are available from @ainative/next-sdk/client when using the Next.js SDK.
// app/layout.tsx
import { AINativeProvider } from '@ainative/next-sdk/client';
import { getApiKey } from '@ainative/next-sdk/server';
export default async function RootLayout({ children }) {
const apiKey = await getApiKey();
return (
<html>
<body>
<AINativeProvider config={{ apiKey }}>
{children}
</AINativeProvider>
</body>
</html>
);
}
// app/components/Chat.tsx
'use client';
import { useChat } from '@ainative/next-sdk/client';
See the Next.js SDK page for server-side patterns including createServerClient, withAuth, and streaming Server Components.
Error handling
All hooks expose an error field of type AINativeError:
interface AINativeError {
message: string;
status?: number; // HTTP status code
code?: string; // API error code
}
Common status codes:
| Status | Meaning |
|---|---|
401 | Invalid or expired API key |
402 | Insufficient credits |
422 | Validation error — check request parameters |
429 | Rate limit exceeded |
503 | Service temporarily unavailable |