Skip to main content

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

NameTypeDefaultDescription
configAINativeConfigrequiredAPI configuration
config.apiKeystringrequiredYour AINative JWT token or API key
config.baseUrlstring'https://api.ainative.studio/api/v1'Override the API base URL
childrenReactNoderequiredComponent 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

NameTypeDefaultDescription
modelstringPreferred model identifier (e.g. 'meta-llama/llama-3.3-70b-instruct')
temperaturenumberSampling temperature (0–2)
max_tokensnumberMaximum tokens to generate
onError(error: AINativeError) => voidCallback invoked on request failure
onSuccess(response: ChatCompletionResponse) => voidCallback invoked on successful completion

Return values

NameTypeDescription
messagesMessage[]Current conversation messages (user + assistant turns)
isLoadingbooleantrue while a request is in flight
errorAINativeError | nullLast error, or null if none
responseChatCompletionResponse | nullRaw 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() => voidClear 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

NameSignatureDescription
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

NameTypeDescription
entityIdstring (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

NameSignatureDescription
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

NameSignatureDescription
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

NameTypeDefaultDescription
pollIntervalnumber2000Polling 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

NameSignatureDescription
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:

StatusMeaning
401Invalid or expired API key
402Insufficient credits
422Validation error — check request parameters
429Rate limit exceeded
503Service temporarily unavailable