Skip to main content

@agentskit/react

React chat UI built on @agentskit/core. Provides three hooks and seven headless components styled via CSS variables.

When to use

  • Browser streaming chat with pluggable LLM adapters and optional tools, memory, RAG, and skills.
  • You want headless markup (data-ak-*) and your own CSS or design system.

Consider @agentskit/ink for terminal apps and @agentskit/runtime for headless agents without React.

Install

npm install @agentskit/react @agentskit/core
# optional: real AI providers
npm install @agentskit/adapters

Hooks

useChat

The primary hook. Creates and manages a full chat session.

import { useChat } from '@agentskit/react'

const chat = useChat({
adapter: myAdapter, // required — AdapterFactory
systemPrompt: 'You are...', // optional
memory: myMemory, // optional — ChatMemory
tools: [...], // optional — ToolDefinition[]
})

useChat configuration (ChatConfig)

OptionTypeDescription
adapterAdapterFactoryRequired. Provider factory from @agentskit/adapters or custom.
systemPromptstringPrepended as a system message when sending.
temperaturenumberPassed through to the adapter when supported.
maxTokensnumberUpper bound on completion length when supported.
toolsToolDefinition[]Functions the model may call; results stream back as tool messages.
skillsSkillDefinition[]Augment system prompt and inject skill tools before send.
memoryChatMemoryPersist and reload Message[] across sessions (Memory).
retrieverRetrieverInjects retrieved context each turn (RAG).
initialMessagesMessage[]Seed the transcript before first user message.
onMessagecallbackInvoked with each persisted Message as the controller updates history.
onErrorcallbackStream or tool errors.
onToolCallcallbackObserve or intercept tool execution (Tools).
observersObserver[]Low-level event stream (Observability).

The hook also exposes controller methods such as approve / deny for human-in-the-loop tools when the underlying tool definitions request confirmation.

Returns a ChatReturn object:

PropertyTypeDescription
messagesMessage[]Full conversation history
inputstringCurrent input field value
status'idle' | 'streaming' | 'error'Session status
errorError | nullLast error, if any
send(text)(text: string) => voidSend a message
stop()() => voidAbort the current stream
retry()() => voidRetry the last request
setInput(val)(val: string) => voidUpdate the input value
clear()() => voidClear the conversation
approve(id) / deny(id, reason?)Confirm or reject pending tool calls when applicable.

useStream

Lower-level hook for consuming a single StreamSource directly.

import { useStream } from '@agentskit/react'

const { text, status, error, stop } = useStream(source, {
onChunk: (chunk) => console.log(chunk),
onComplete: (full) => console.log('done', full),
onError: (err) => console.error(err),
})

useReactive

Reactive state container that triggers re-renders on property mutations.

import { useReactive } from '@agentskit/react'

const state = useReactive({ count: 0, label: 'hello' })
// Mutate directly — component re-renders automatically
state.count++

Complete example (demo adapter — no API key needed)

import { useChat, ChatContainer, Message, InputBar, ThinkingIndicator } from '@agentskit/react'
import type { AdapterFactory } from '@agentskit/react'
import '@agentskit/react/theme'

function createDemoAdapter(): AdapterFactory {
return {
createSource: ({ messages }) => {
let cancelled = false
return {
stream: async function* () {
const last = [...messages].reverse().find(m => m.role === 'user')
const reply = `You said: "${last?.content ?? ''}". This is a demo response.`
for (const chunk of reply.match(/.{1,20}/g) ?? []) {
if (cancelled) return
await new Promise(r => setTimeout(r, 40))
yield { type: 'text' as const, content: chunk }
}
yield { type: 'done' as const }
},
abort: () => { cancelled = true },
}
},
}
}

export default function App() {
const chat = useChat({
adapter: createDemoAdapter(),
systemPrompt: 'You are a helpful assistant.',
})

return (
<ChatContainer>
{chat.messages.map(msg => (
<Message key={msg.id} message={msg} />
))}
<ThinkingIndicator visible={chat.status === 'streaming'} />
<InputBar chat={chat} placeholder="Say something..." />
</ChatContainer>
)
}

Swap to a real provider

Replace the adapter — nothing else changes:

import { anthropic } from '@agentskit/adapters'

const chat = useChat({
adapter: anthropic({ apiKey: process.env.ANTHROPIC_API_KEY, model: 'claude-sonnet-4-6' }),
})
import { openai } from '@agentskit/adapters'

const chat = useChat({
adapter: openai({ apiKey: process.env.OPENAI_API_KEY, model: 'gpt-4o' }),
})

data-ak-* attributes

Every component emits data-ak-* attributes so you can style or target them without class names:

AttributeElementValues
data-ak-chat-containerwrapper <div>
data-ak-messagemessage wrapper
data-ak-rolemessage wrapperuser, assistant, system, tool
data-ak-statusmessage wrapperidle, streaming, done, error
data-ak-contentmessage body
data-ak-avataravatar slot
data-ak-actionsactions slot
data-ak-input-barform wrapper
data-ak-inputtextarea
data-ak-sendsubmit button
data-ak-thinkingthinking div
data-ak-markdownmarkdown wrapper
data-ak-streamingmarkdown wrappertrue when streaming
data-ak-code-blockcode block wrapper
data-ak-languagecode block wrapperlanguage string
data-ak-copycopy button
data-ak-tool-calltool call wrapper
data-ak-tool-statustool call wrapperpending, running, done, error

See Theming for full CSS variable reference.

Composition

  • Prefer small presentational wrappers around ChatContainer, Message, and InputBar rather than forking internals.
  • Use data-ak-* for theme tokens; for MUI/shadcn, see MUI Chat and shadcn Chat.
  • ToolCallView and Markdown accept standard props — pair with your router for deep links inside assistant content.

Production notes

  • Keep API keys on the server when possible (route handlers, server actions); use vercelAI or a thin BFF that returns a stream.
  • Align @agentskit/* versions on the same minor release to avoid type drift with core.

Troubleshooting

SymptomLikely cause
Double messages in React Strict ModeExpected during dev; production should match single mount. If not, ensure a single useChat per session id.
Stream stuck on streamingAdapter did not yield { type: 'done' } or network hung; call stop() and inspect adapter abort.
Tools never invokedWeak description / schema; model may ignore. Tighten schema and system prompt.
Styles missingImport @agentskit/react/theme or define CSS variables from Theming.

See also

Start here · Packages · TypeDoc (@agentskit/react) · Components · Theming · Ink · @agentskit/core