Skip to main content
The SDK includes React hooks for building chat interfaces. The useAgent hook manages message state, streaming, and session lifecycle automatically.

Installation

The React integration is available at the /react subpath:
npm install @superserve/sdk
React 18+ is required as a peer dependency.

useAgent Hook

The useAgent hook provides a complete chat interface state manager:
import { useAgent } from '@superserve/sdk/react'

function Chat() {
  const { messages, sendMessage, status, isStreaming, stop, reset } = useAgent({
    agent: 'my-agent',
    apiKey: 'ss_...'
  })

  return (
    <div>
      <div className="messages">
        {messages.map(msg => (
          <div key={msg.id} className={msg.role}>
            <strong>{msg.role}:</strong> {msg.content}
          </div>
        ))}
      </div>
      
      {isStreaming && <button onClick={stop}>Stop</button>}
      
      <button onClick={() => sendMessage('Hello!')}>Send</button>
      <button onClick={reset}>Clear</button>
    </div>
  )
}

Options

agent
string
required
Agent name or ID to interact with
apiKey
string
API key for authentication. Can also be provided via SuperserveProvider.
baseUrl
string
Base URL for the Superserve API. Defaults to https://api.superserve.ai
initialMessages
Message[]
Initial messages to populate the chat. Useful for resuming conversations.
onFinish
(message: Message) => void
Callback invoked when the agent finishes responding
onError
(error: Error) => void
Callback invoked on errors

Return Value

The hook returns an object with the following properties:
messages
Message[]
Array of messages in the conversation
sendMessage
(content: string) => void
Send a message to the agent. Does nothing if already streaming.
status
'ready' | 'streaming' | 'error'
Current status of the agent interaction
isStreaming
boolean
Whether the agent is currently streaming a response. Equivalent to status === 'streaming'.
error
Error | null
The current error, if any
stop
() => void
Abort the current streaming response
reset
() => void
Clear all messages and end the session. Resets to initialMessages if provided.

SuperserveProvider

Use SuperserveProvider to share configuration across multiple useAgent hooks:
import { SuperserveProvider } from '@superserve/sdk/react'

function App() {
  return (
    <SuperserveProvider apiKey={process.env.REACT_APP_SUPERSERVE_API_KEY}>
      <Chat />
      <AnotherChat />
    </SuperserveProvider>
  )
}

function Chat() {
  // apiKey is inherited from SuperserveProvider
  const { messages, sendMessage } = useAgent({
    agent: 'my-agent'
  })
  
  // ...
}

Provider Props

apiKey
string
API key to share with all child hooks
baseUrl
string
Base URL to share with all child hooks
children
ReactNode
required
React children

Examples

Basic Chat Interface

import { useAgent } from '@superserve/sdk/react'
import { useState } from 'react'

function Chat() {
  const [input, setInput] = useState('')
  const { messages, sendMessage, isStreaming } = useAgent({
    agent: 'my-agent',
    apiKey: process.env.REACT_APP_SUPERSERVE_API_KEY
  })

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault()
    if (input.trim() && !isStreaming) {
      sendMessage(input)
      setInput('')
    }
  }

  return (
    <div className="chat">
      <div className="messages">
        {messages.map(msg => (
          <div key={msg.id} className={`message ${msg.role}`}>
            <div className="content">{msg.content}</div>
            {msg.toolCalls && msg.toolCalls.length > 0 && (
              <div className="tools">
                {msg.toolCalls.map((tool, i) => (
                  <span key={i} className="tool">
                    {tool.name}
                  </span>
                ))}
              </div>
            )}
          </div>
        ))}
      </div>
      
      <form onSubmit={handleSubmit}>
        <input
          value={input}
          onChange={(e) => setInput(e.target.value)}
          placeholder="Type a message..."
          disabled={isStreaming}
        />
        <button type="submit" disabled={isStreaming || !input.trim()}>
          {isStreaming ? 'Sending...' : 'Send'}
        </button>
      </form>
    </div>
  )
}

With Error Handling

function Chat() {
  const { messages, sendMessage, status, error, isStreaming, reset } = useAgent({
    agent: 'my-agent',
    apiKey: 'ss_...',
    onError: (error) => {
      console.error('Chat error:', error)
    }
  })

  return (
    <div>
      {status === 'error' && (
        <div className="error">
          <p>Error: {error?.message}</p>
          <button onClick={reset}>Try Again</button>
        </div>
      )}
      
      <div className="messages">
        {messages.map(msg => (
          <div key={msg.id}>{msg.content}</div>
        ))}
      </div>
      
      {isStreaming && <div className="loading">Agent is typing...</div>}
      
      <button onClick={() => sendMessage('Hello')}>Send</button>
    </div>
  )
}

Resuming Conversations

function Chat({ userId }: { userId: string }) {
  // Load previous messages from localStorage
  const initialMessages = useMemo(() => {
    const saved = localStorage.getItem(`chat-${userId}`)
    return saved ? JSON.parse(saved) : []
  }, [userId])

  const { messages, sendMessage } = useAgent({
    agent: 'my-agent',
    apiKey: 'ss_...',
    initialMessages,
    onFinish: (message) => {
      // Save to localStorage after each response
      localStorage.setItem(`chat-${userId}`, JSON.stringify(messages))
    }
  })

  return (
    <div>
      {messages.map(msg => (
        <div key={msg.id}>{msg.content}</div>
      ))}
    </div>
  )
}

Progress Indicators

import { useAgent } from '@superserve/sdk/react'

function Chat() {
  const { messages, sendMessage, isStreaming, stop } = useAgent({
    agent: 'my-agent',
    apiKey: 'ss_...'
  })

  return (
    <div>
      <div className="messages">
        {messages.map(msg => (
          <div key={msg.id} className={msg.role}>
            {msg.content}
            {msg.toolCalls && (
              <div className="tools">
                <strong>Tools used:</strong>
                {msg.toolCalls.map((tool, i) => (
                  <span key={i}>
                    {tool.name} ({tool.duration}ms)
                  </span>
                ))}
              </div>
            )}
          </div>
        ))}
        
        {isStreaming && (
          <div className="streaming">
            <span className="spinner"></span>
            <span>Agent is working...</span>
            <button onClick={stop}>Stop</button>
          </div>
        )}
      </div>
      
      <button onClick={() => sendMessage('Analyze this code')}>Send</button>
    </div>
  )
}

Multiple Agents

import { SuperserveProvider, useAgent } from '@superserve/sdk/react'

function App() {
  return (
    <SuperserveProvider apiKey="ss_...">
      <div className="agents">
        <AgentChat agent="code-reviewer" title="Code Reviewer" />
        <AgentChat agent="documentation" title="Documentation" />
      </div>
    </SuperserveProvider>
  )
}

function AgentChat({ agent, title }: { agent: string; title: string }) {
  const { messages, sendMessage, isStreaming } = useAgent({ agent })

  return (
    <div className="agent-chat">
      <h2>{title}</h2>
      <div className="messages">
        {messages.map(msg => (
          <div key={msg.id}>{msg.content}</div>
        ))}
      </div>
      <button 
        onClick={() => sendMessage('Hello')}
        disabled={isStreaming}
      >
        Send
      </button>
    </div>
  )
}

Streaming with Markdown

import { useAgent } from '@superserve/sdk/react'
import ReactMarkdown from 'react-markdown'

function Chat() {
  const { messages, sendMessage } = useAgent({
    agent: 'my-agent',
    apiKey: 'ss_...'
  })

  return (
    <div className="messages">
      {messages.map(msg => (
        <div key={msg.id} className={msg.role}>
          {msg.role === 'assistant' ? (
            <ReactMarkdown>{msg.content}</ReactMarkdown>
          ) : (
            <p>{msg.content}</p>
          )}
        </div>
      ))}
    </div>
  )
}

TypeScript Types

All types are exported for use in your application:
import type {
  Message,
  AgentStatus,
  UseAgentOptions,
  UseAgentReturn
} from '@superserve/sdk/react'

const messages: Message[] = [
  {
    id: 'msg_1',
    role: 'user',
    content: 'Hello',
    createdAt: new Date()
  }
]

const status: AgentStatus = 'ready'

Message Type

interface Message {
  id: string
  role: 'user' | 'assistant'
  content: string
  toolCalls?: ToolCall[]
  createdAt: Date
}

AgentStatus Type

type AgentStatus = 'ready' | 'streaming' | 'error'

Best Practices

If you have multiple useAgent hooks in your app, use SuperserveProvider to share the API key:
<SuperserveProvider apiKey={apiKey}>
  <ChatA />
  <ChatB />
</SuperserveProvider>
Prevent duplicate messages by disabling the input while streaming:
<input disabled={isStreaming} />
<button disabled={isStreaming}>Send</button>
Always show error messages to users and provide a way to recover:
{status === 'error' && (
  <div className="error">
    {error?.message}
    <button onClick={reset}>Try Again</button>
  </div>
)}
Save messages to localStorage or a database to resume conversations:
const { messages } = useAgent({
  agent: 'my-agent',
  apiKey: 'ss_...',
  onFinish: () => {
    localStorage.setItem('messages', JSON.stringify(messages))
  }
})
Show loading indicators, typing animations, or tool usage during streaming:
{isStreaming && (
  <div className="typing">
    <span>Agent is typing</span>
    <span className="dots">...</span>
  </div>
)}

Next Steps

Client Reference

Learn about the core SDK client

Streaming

Understand streaming events and callbacks

Build docs developers (and LLMs) love