---
title: "AI Kit React Hooks"
description: "Complete hook reference for @ainative/react-sdk — useChat, useAgent, useCredits, useMemory, useThread, useTask, useAINative"
canonical: "https://docs.ainative.studio/docs/ai-kit/hooks"
last-updated: "2026-10-22T17:48:46.689Z"
---

# AI Kit React Hooks

Source: https://docs.ainative.studio/docs/ai-kit/hooks

> Complete hook reference for @ainative/react-sdk — useChat, useAgent, useCredits, useMemory, useThread, useTask, useAINative

# React Hooks

All hooks are provided by `@ainative/react-sdk` and require the `AINativeProvider` to be present in the component tree.

```bash
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.

```tsx
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

```typescript
function useAINative(): AINativeClient
```

### Return type

```typescript
interface AINativeClient {
  config: AINativeConfig;  // { apiKey, baseUrl? }
  baseUrl: string;         // Resolved base URL
}
```

### Example

```tsx
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

```typescript
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

```tsx
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

```typescript
function useAgent(): UseAgentReturn
```

### Return type

```typescript
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

```typescript
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

```tsx
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

```typescript
function useCredits(): UseCreditsReturn
```

### Return type

```typescript
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

```tsx
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

```typescript
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

```typescript
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

```typescript
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

```tsx
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

```typescript
function useThread(): UseThreadReturn
```

### Return type

```typescript
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

```typescript
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

```tsx
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

```typescript
function useTask(options?: UseTaskOptions): UseTaskReturn
```

### Options

| Name | Type | Default | Description |
|------|------|---------|-------------|
| `pollInterval` | `number` | `2000` | Polling interval in milliseconds while a task is active |

### Return type

```typescript
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

```typescript
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

```tsx
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.

```tsx
// 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>
  );
}
```

```tsx
// app/components/Chat.tsx
'use client';
import { useChat } from '@ainative/next-sdk/client';
```

The AI Kit React SDK also supports Next.js server-side patterns including `createServerClient`, `withAuth`, and streaming Server Components.

---

## Error handling

All hooks expose an `error` field of type `AINativeError`:

```typescript
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 |
