Package Exports
- @dudko.dev/agent-web-react
- @dudko.dev/agent-web-react/package.json
- @dudko.dev/agent-web-react/styles.css
Readme
@dudko.dev/agent-web-react
React bindings for @dudko.dev/agent-web β
the headless, universal in-browser LLM agent. Drop the agent into any React
app with a single hook and (optionally) a set of pre-styled components:
- πͺ
useAgentβ build the agent from a config, stream its typed events into a ready-to-render state (plan, steps, tool calls, streamed answer, token usage, model-load progress, chat transcript), and getrun/stop/reset/reload. - π§©
<AgentProvider>+ components β a drop-in<AgentChat>panel plus<PlanView>,<StepList>,<Composer>,<ModelLoadBar>,<UsageBadge>, and<ApiKeyForm>. Bring your own CSS or import the optional stylesheet. - π
useCredentialsβ store BYOK API keys encrypted at rest (WebCrypto + IndexedDB). - π₯οΈ
useWebLLMModelβ load a local WebGPU model with download progress. - ποΈ Headless-first β the eventβUI logic is a pure, exported reducer
(
agentStateReducer); the components are optional sugar on top.
βΆοΈ Live demo β an agent that edits a sticky-notes board via tools. Cloud BYOK or local WebGPU, all in your browser. Source in
demo/.
Install
npm install @dudko.dev/agent-web-react @dudko.dev/agent-web react react-domThen add only the model providers you use (optional peers of the core, dynamically imported):
# cloud, pick what you need
npm install @ai-sdk/openai # or @ai-sdk/anthropic, @ai-sdk/google, @ai-sdk/xai, @ai-sdk/deepseek, @ai-sdk/openai-compatible
# local WebGPU models
npm install @browser-ai/web-llm @mlc-ai/web-llmImport the optional stylesheet once (skip it if you style the components yourself):
import '@dudko.dev/agent-web-react/styles.css'Quick start β a drop-in chat panel
<AgentProvider> builds one agent and shares it; <AgentChat> renders it.
import { useMemo } from 'react'
import {
AgentProvider,
AgentChat,
useCredentials,
defineTool,
} from '@dudko.dev/agent-web-react'
import { z } from 'zod'
import '@dudko.dev/agent-web-react/styles.css'
export function Assistant() {
const credentials = useCredentials() // encrypted vault (WebCrypto + IndexedDB)
// Your app's actions, as tools. Keep the object referentially stable.
const tools = useMemo(
() => ({
add_text: defineTool({
description: 'Add a text block to the page.',
inputSchema: z.object({ text: z.string() }),
execute: async ({ text }) => addTextBlock(text), // your code
}),
}),
[],
)
return (
<AgentProvider
config={{
model: { providerType: 'google', model: 'gemini-2.0-flash', credentialRef: 'google' },
credentials: credentials.store,
tools,
describeState: () => serializeMyState(), // optional grounding
}}
>
<AgentChat title="Assistant" style={{ height: 520 }} />
</AgentProvider>
)
}Store the user's key once (encrypted at rest), e.g. from a settings form:
const credentials = useCredentials()
await credentials.setKey('google', userProvidedKey)
// or drop in <ApiKeyForm credentials={credentials} credentialRef="google" />The useAgent hook (headless)
useAgent is the whole library in one hook β use it directly if you want your
own UI:
import { useAgent } from '@dudko.dev/agent-web-react'
function Custom() {
const agent = useAgent({
model: { providerType: 'openai', model: 'gpt-4o-mini', credentialRef: 'openai' },
credentials,
tools,
})
return (
<>
<button disabled={!agent.isReady} onClick={() => agent.run('Add a totals row')}>
Run
</button>
{agent.isRunning && <button onClick={agent.stop}>Stop</button>}
{/* Everything below is live, derived from the event stream: */}
{agent.plan && <p>{agent.plan.thought}</p>}
{agent.steps.map((s) => (
<div key={s.id}>
{s.index}/{s.total} Β· {s.step.description} Β· {s.status}
{s.toolCalls.map((c, i) => (
<code key={i}>{c.name}</code>
))}
</div>
))}
<p>{agent.finalText}</p>
<small>{agent.usage.totalTokens} tokens</small>
</>
)
}useAgent(config, options) returns the full AgentUiState plus:
| Field | Description |
|---|---|
run(goal) |
Start a run; resolves with the RunResult |
stop() |
Abort the in-flight run |
reset() |
Clear the whole conversation |
reload() |
Rebuild the agent (e.g. after storing a new key) |
status |
idle | initializing | ready | running | error |
isReady / isRunning |
convenience booleans |
messages |
chat transcript ({ role, content, pending }[]) |
plan / steps |
the live plan and per-step tool calls |
finalText |
the streamed final answer |
usage |
running token total |
modelLoad |
WebLLM download progress, when loading |
Options: deps (rebuild the agent when these change β e.g. on a model
switch), onEvent (tap the raw event stream), autoStart, maxEvents.
Keep
toolsanddescribeStatereferentially stable (useMemo/useCallback); pass a changeddepsarray to rebuild the agent on a model/provider switch.
Local WebGPU model (no key, offline)
import { useAgent, useWebLLMModel, ModelLoadBar } from '@dudko.dev/agent-web-react'
function LocalAgent() {
const local = useWebLLMModel('Llama-3.2-3B-Instruct-q4f16_1-MLC')
const agent = useAgent(
{ model: local.model!, tools },
{ deps: [local.ready], autoStart: local.ready }, // build once the model is loaded
)
if (!local.ready) {
return local.loading ? (
<ModelLoadBar load={{ progress: local.progress, text: local.text }} />
) : (
<button disabled={!local.supported} onClick={local.load}>
Load local model
</button>
)
}
return <AgentChat controller={agent} />
}Components
All components are optional and styled by styles.css (class-prefixed awr-,
themeable via --awr-* custom properties, light + dark). Each accepts a
className; data components take plain props so you can use them standalone.
| Component | Purpose |
|---|---|
<AgentChat> |
Full panel: transcript + live activity + composer. Reads a controller prop or the <AgentProvider> context. |
<MessageList> |
The chat transcript. |
<Composer> |
Textarea + send/stop button (Enter to send). |
<PlanView> |
A plan's reasoning + step list. |
<StepList> |
Live execution steps with tool calls. |
<ModelLoadBar> |
WebLLM download/init progress. |
<UsageBadge> |
Compact token readout. |
<ApiKeyForm> |
BYOK key entry that writes to the encrypted vault. |
Build your own UI
The components are a thin layer over the exported, pure reducer. Use it directly if you'd rather render everything yourself:
import { agentStateReducer, createInitialAgentState } from '@dudko.dev/agent-web-react'
let state = createInitialAgentState()
state = agentStateReducer(state, { type: 'event', event }) // fold each AgentEventHow it fits together
@dudko.dev/agent-web (peer) β the headless agent + providers + vault
@dudko.dev/agent-web-react β useAgent / <AgentProvider> / components
your app β tools, credentials, and where the panel goesThe React package re-exports the core primitives you usually need
(createAgent, defineTool, VaultCredentialStore, createWebLLMModel,
isWebGPUAvailable, and the key types), so a React app can import everything
from one place.
Direct browser calls & CORS: not every provider allows direct BYOK calls from a browser origin. Google (Gemini), openai-compatible and the gateway are the reliable direct paths; Anthropic works (a required header is injected); OpenAI/xAI/DeepSeek usually need a proxy. See the core's providers doc. Ship only the user's own key to the browser β shared/app keys belong behind a proxy or the gateway.
Demo & deployment
The demo/ app (Vite + React) is deployed to GitHub Pages by
.github/workflows/deploy-demo.yml on
every push to main. To run it locally:
npm install && npm run build # build the library into dist/
cd demo && npm install && npm run devThe demo aliases @dudko.dev/agent-web-react to the built ../dist, so rebuild
the library (npm run build) after changing its source.
License
MIT Β© Siarhei Dudko