svelte-agents Svelte Themes

Svelte Agents

Native Svelte 5 bindings for the Cloudflare Agents SDK.

svelte-agents

Native Svelte 5 client bindings for the Cloudflare Agents SDK.

svelte-agents mirrors the public client API of Cloudflare's React hooks, but exposes it as Svelte-friendly classes:

  • useAgent(...) from agents/react becomes new Agent(...).
  • useAgentChat(...) from @cloudflare/ai-chat/react becomes new AgentChat(...).
  • useVoiceAgent(...) from @cloudflare/voice/react becomes new VoiceAgent(...) from svelte-agents/voice.
  • useVoiceInput(...) from @cloudflare/voice/react becomes new VoiceInput(...) from svelte-agents/voice.

The intent is React parity, not a new protocol. Your server-side agents, routing, callable methods, state synchronization, AI chat persistence, tool calling, and human-in-the-loop approval flows stay on the Cloudflare Agents SDK. This package only replaces the React client layer with Svelte 5 runes-based classes.

Install

bun add svelte-agents

You still build and deploy the server with the Cloudflare Agents SDK. For example, use Agent, AIChatAgent, routeAgentRequest, getAgentByName, callable, and AI SDK tools from the Cloudflare packages documented in the Agents SDK.

Agent

Cloudflare React:

import { useAgent } from 'agents/react';

function Counter() {
    const agent = useAgent<{ count: number }>({
        agent: 'CounterAgent',
        name: 'room-123',
        onStateUpdate: (state) => {
            console.log('New state:', state);
        }
    });

    return <button onClick={() => agent.stub.increment()}>{agent.name}</button>;
}

Svelte equivalent:

<script lang="ts">
    import { Agent } from 'svelte-agents';

    const agent = new Agent<{ increment: () => Promise<number> }, { count: number }>({
        agent: 'CounterAgent',
        name: 'room-123',
        onStateUpdate: (state) => {
            console.log('New state:', state);
        }
    });
</script>

<button onclick={() => agent.stub.increment()}>
    {agent.name}
</button>

Agent connects to the same /agents/{agent-name}/{instance-name} WebSocket route used by useAgent. Agent class names are converted to kebab-case to match routeAgentRequest, so CounterAgent connects as counter-agent.

Agent Options

These options match Cloudflare's useAgent(options) client API.

Option Type Description
agent string Required agent class name. Converted to kebab-case for routing.
name string Instance name. Defaults to "default".
host string Custom host for the Worker.
path string Additional path appended to the agent URL.
basePath string Full URL path for custom routing, bypassing default /agents/... construction.
query Record<string, string | null> | () => Promise<Record<string, string | null>> Query parameters for the connection.
queryDeps unknown[] Cache dependencies for async query values.
cacheTtl number Query cache TTL in milliseconds. Defaults to five minutes.
onStateUpdate (state, source) => void Called when state changes from the server or local client.
onStateUpdateError (error) => void Called when a state update error is sent by the agent.
onMcpUpdate (mcpServers) => void Called when MCP server state is synchronized.
onOpen (event) => void WebSocket open callback.
onClose (event) => void WebSocket close callback.
onError (event) => void WebSocket error callback.
onMessage (event) => void Raw WebSocket message callback.
onIdentity (name, agent) => void Called when the server sends identity.
onIdentityChange (oldName, newName, oldAgent, newAgent) => void Called when identity changes after reconnect.
enabled boolean Enables or disables the connection.

Agent Instance API

Property or method Description
agent.agent Current agent name, reactive.
agent.name Current instance name, reactive.
agent.path Agent/sub-agent path chain, reactive.
agent.state Latest synchronized state, reactive.
agent.identified Whether server identity has been received, reactive.
agent.ready Promise that resolves when identity is received.
agent.readyState Current WebSocket ready state.
agent.url WebSocket URL.
agent.bufferedAmount WebSocket buffered amount.
agent.setState(state) Push state to the agent.
agent.call(method, args?, options?) Call an agent method over RPC.
agent.stub Proxy for typed method calls, matching Cloudflare's createStubProxy.
agent.send(data) Send a raw WebSocket message.
agent.close(code?, reason?) Close the connection.
agent.reconnect(code?, reason?) Force reconnection.
agent.addEventListener(...) Attach a socket event listener.
agent.removeEventListener(...) Remove a socket event listener.
agent.getHttpUrl() Get the HTTP URL for the same agent route.

Custom Routing and Identity

Cloudflare React:

import { useAgent } from 'agents/react';

const agent = useAgent({
    agent: 'UserAgent',
    basePath: 'user',
    onIdentity: (name, agentType) => {
        console.log(`Connected to ${agentType} instance: ${name}`);
    }
});

Svelte equivalent:

<script lang="ts">
    import { Agent } from 'svelte-agents';

    const agent = new Agent({
        agent: 'UserAgent',
        basePath: 'user',
        onIdentity: (name, agentType) => {
            console.log(`Connected to ${agentType} instance: ${name}`);
        }
    });
</script>

{#if agent.identified}
    <p>Connected to: {agent.name}</p>
{:else}
    <p>Connecting...</p>
{/if}

When using basePath, the server chooses the instance and sends identity back to the client. agent.ready resolves when that identity arrives, matching the Cloudflare routing docs.

AgentChat

AgentChat is the Svelte equivalent of Cloudflare's useAgentChat. It wraps @ai-sdk/svelte's Chat class with the same WebSocket transport semantics used by the React hook.

Cloudflare React:

import { useAgent } from 'agents/react';
import { useAgentChat } from '@cloudflare/ai-chat/react';

function Chat() {
    const agent = useAgent({ agent: 'ChatAgent' });
    const {
        messages,
        sendMessage,
        clearHistory,
        addToolOutput,
        addToolApprovalResponse,
        setMessages,
        status
    } = useAgentChat({ agent });

    return (
        <form
            onSubmit={(event) => {
                event.preventDefault();
                const input = event.currentTarget.elements.namedItem('input') as HTMLInputElement;
                sendMessage({ text: input.value });
                input.value = '';
            }}
        >
            <input name="input" />
            <button disabled={status === 'streaming'}>Send</button>
        </form>
    );
}

Svelte equivalent:

<script lang="ts">
    import { Agent, AgentChat } from 'svelte-agents';

    const agent = new Agent({ agent: 'ChatAgent' });
    const chat = new AgentChat({ agent });

    let input = $state('');

    function submit() {
        if (!input.trim()) return;
        chat.sendMessage({ text: input });
        input = '';
    }
</script>

{#each chat.messages as message (message.id)}
    <div>
        <strong>{message.role}:</strong>
        {#each message.parts as part}
            {#if part.type === 'text'}
                <span>{part.text}</span>
            {/if}
        {/each}
    </div>
{/each}

<form
    onsubmit={(event) => {
        event.preventDefault();
        submit();
    }}
>
    <input bind:value={input} placeholder="Type a message..." />
    <button disabled={chat.isStreaming}>Send</button>
</form>

AgentChat Options

These options match Cloudflare's useAgentChat(options) API.

Option Default Description
agent Required An Agent connection.
messages undefined Initial messages supplied by the client.
onToolCall undefined Handles client-side tool execution.
autoContinueAfterToolResult true Continue the conversation after client tool results and approvals.
resume true Resume interrupted streams on reconnect.
body undefined Object or function merged into every chat request body.
prepareSendMessagesRequest undefined Per-request customization for body, headers, credentials, or API URL.
getInitialMessages undefined Custom initial message loader. Set to null to skip the HTTP fetch.
credentials undefined Fetch credentials for message requests.
headers undefined Fetch headers for message requests.

Deprecated React-parity options are still accepted and warn when used: tools, toolsRequiringConfirmation, experimental_automaticToolResolution, and autoSendAfterAllConfirmationsResolved. Cloudflare's current docs recommend defining tools on the server with the AI SDK tool() function and using needsApproval or onToolCall.

AgentChat Instance API

Property or method Description
chat.id Underlying chat id.
chat.messages Current UIMessage[], reactive.
chat.status AI SDK status: "idle", "submitted", "streaming", or "error".
chat.error Current chat error.
chat.lastMessage Last message in the conversation.
chat.isServerStreaming true for server-initiated streams.
chat.isStreaming true for either client or server streaming.
chat.isToolContinuation true while continuing after tool output.
chat.sendMessage(message, options?) Send a user message.
chat.regenerate(options?) Regenerate a response.
chat.resumeStream(options?) Resume a stream.
chat.clearError() Clear the current error.
chat.stop() Stop the current stream.
chat.clearHistory() Clear client and server chat history.
chat.setMessages(messagesOrUpdater) Set messages locally and broadcast them to the agent.
chat.addToolOutput({ toolCallId, output, state?, errorText? }) Provide client-side tool output or a custom tool error.
chat.addToolApprovalResponse({ id, approved }) Approve or reject a tool requiring approval.
chat.addToolResult(...) Deprecated alias for addToolOutput.
chat.destroy() Remove listeners and stop Svelte effects. Call this for manually managed lifetimes outside components.

Tools and Human-in-the-Loop

Cloudflare React:

const { messages, addToolApprovalResponse } = useAgentChat({ agent });

messages
    .flatMap((message) => message.parts)
    .filter((part) => part.type === 'tool' && part.state === 'approval-required')
    .map((part) => (
        <button onClick={() => addToolApprovalResponse({ id: part.toolCallId, approved: true })}>
            Approve
        </button>
    ));

Svelte equivalent:

<script lang="ts">
    import { getToolCallId, getToolPartState } from 'svelte-agents';
</script>

{#each chat.messages
    .flatMap((message) => message.parts)
    .filter((part) => part.type === 'tool' && getToolPartState(part) === 'waiting-approval') as part}
    {@const toolCallId = getToolCallId(part)}
    <button onclick={() => chat.addToolApprovalResponse({ id: toolCallId, approved: true })}>
        Approve
    </button>
{/each}

Client-side tools use the same onToolCall pattern as the React hook:

<script lang="ts">
    import { Agent, AgentChat } from 'svelte-agents';

    const agent = new Agent({ agent: 'ChatAgent' });
    const chat = new AgentChat({
        agent,
        onToolCall: async ({ toolCall, addToolOutput }) => {
            if (toolCall.toolName === 'getLocation') {
                const position = await new Promise<GeolocationPosition>((resolve, reject) => {
                    navigator.geolocation.getCurrentPosition(resolve, reject);
                });

                addToolOutput({
                    toolCallId: toolCall.toolCallId,
                    output: {
                        lat: position.coords.latitude,
                        lng: position.coords.longitude
                    }
                });
            }
        }
    });
</script>

Voice

Voice bindings live on the svelte-agents/voice subpath so the root package can stay focused on text agents and chat:

import { VoiceAgent, VoiceInput } from 'svelte-agents/voice';

Server-side voice agents still come from Cloudflare's voice package:

import { Agent } from 'agents';
import { withVoice, WorkersAIFluxSTT, WorkersAITTS } from '@cloudflare/voice';

VoiceAgent

VoiceAgent is the Svelte equivalent of Cloudflare's useVoiceAgent. It wraps VoiceClient for withVoice agents and manages connection state, microphone capture, playback, silence detection, interrupt detection, transcripts, metrics, and custom app messages.

Cloudflare React:

import { useVoiceAgent } from '@cloudflare/voice/react';

function VoiceUI() {
    const { status, connected, audioLevel, isMuted, startCall, endCall, toggleMute } = useVoiceAgent({
        agent: 'MyAgent',
        name: 'default',
        host: window.location.host
    });

    return (
        <>
            <p>Status: {status}</p>
            <p>Connected: {String(connected)}</p>
            <p>Audio level: {audioLevel}</p>
            <button onClick={status === 'idle' ? startCall : endCall}>
                {status === 'idle' ? 'Start call' : 'End call'}
            </button>
            <button onClick={toggleMute}>{isMuted ? 'Unmute' : 'Mute'}</button>
        </>
    );
}

Svelte equivalent:

<script lang="ts">
    import { VoiceAgent } from 'svelte-agents/voice';

    const voice = new VoiceAgent({
        agent: 'MyAgent',
        name: 'default',
        host: window.location.host
    });
</script>

<p>Status: {voice.status}</p>
<p>Connected: {String(voice.connected)}</p>
<p>Audio level: {voice.audioLevel}</p>

<button onclick={voice.status === 'idle' ? voice.startCall : voice.endCall}>
    {voice.status === 'idle' ? 'Start call' : 'End call'}
</button>

<button onclick={voice.toggleMute}>
    {voice.isMuted ? 'Unmute' : 'Mute'}
</button>

VoiceAgent Options

Option Default Description
agent Required Agent class name.
name "default" Instance name.
host window.location.host Worker host.
query undefined Query parameters appended to the WebSocket URL.
transport undefined Custom voice transport. Defaults to WebSocket via PartySocket.
audioInput undefined Custom audio input source for WebRTC, SFU, or other routing.
preferredFormat undefined Preferred server audio format hint.
silenceThreshold 0.04 RMS below this is silence.
silenceDurationMs 500 Silence duration before end-of-speech.
interruptThreshold 0.05 RMS threshold for detecting speech during playback.
interruptChunks 2 Consecutive high-RMS chunks needed to interrupt playback.
maxTranscriptMessages 200 Maximum transcript messages kept by VoiceClient.
onReconnect undefined Called when voice.update(...) changes the connection key.

VoiceAgent Instance API

Property or method Description
voice.status "idle", "listening", "thinking", or "speaking".
voice.transcript TranscriptMessage[] conversation history.
voice.interimTranscript Current partial transcript, or null.
voice.metrics Latest pipeline timing metrics, or null.
voice.audioLevel Current microphone RMS level from 0 to 1.
voice.isMuted Whether microphone audio is muted.
voice.connected Whether the voice transport is connected.
voice.error Current error message, or null.
voice.lastCustomMessage Last non-voice-protocol message from the server.
voice.startCall() Request microphone access and begin streaming audio.
voice.endCall() End the call and release microphone resources.
voice.toggleMute() Toggle microphone mute.
voice.sendText(text) Send text directly, bypassing STT.
voice.sendJSON(data) Send arbitrary JSON app messages to the agent.
voice.update(options) Reconnect when connection identity or tuning options change.
voice.destroy() Remove listeners and disconnect the underlying VoiceClient.

VoiceInput

VoiceInput is the Svelte equivalent of Cloudflare's useVoiceInput. It is optimized for dictation and speech-to-text UI: it accumulates user transcripts into a single string and ignores assistant/TTS responses.

Cloudflare React:

import { useVoiceInput } from '@cloudflare/voice/react';

function Dictation() {
    const { transcript, interimTranscript, isListening, start, stop, clear } = useVoiceInput({
        agent: 'DictationAgent'
    });

    return (
        <>
            <textarea value={transcript + (interimTranscript ? ' ' + interimTranscript : '')} readOnly />
            <button onClick={isListening ? stop : start}>{isListening ? 'Stop' : 'Dictate'}</button>
            <button onClick={clear}>Clear</button>
        </>
    );
}

Svelte equivalent:

<script lang="ts">
    import { VoiceInput } from 'svelte-agents/voice';

    const voice = new VoiceInput({ agent: 'DictationAgent' });
</script>

<textarea
    value={voice.transcript + (voice.interimTranscript ? ` ${voice.interimTranscript}` : '')}
    readonly
/>

<button onclick={voice.isListening ? voice.stop : voice.start}>
    {voice.isListening ? 'Stop' : 'Dictate'}
</button>

<button onclick={voice.clear}>Clear</button>

VoiceInput Options

Option Default Description
agent Required Agent class name.
name "default" Instance name.
host window.location.host Worker host.
silenceThreshold 0.04 RMS below this is silence.
silenceDurationMs 500 Silence duration before end-of-speech.

VoiceInput Instance API

Property or method Description
voice.transcript Accumulated final user transcript text.
voice.interimTranscript Current partial transcript, or null.
voice.isListening Whether the mic is actively listening.
voice.audioLevel Current microphone RMS level from 0 to 1.
voice.isMuted Whether microphone audio is muted.
voice.error Current error message, or null.
voice.start() Request microphone access and begin streaming audio.
voice.stop() Stop listening and release microphone resources.
voice.toggleMute() Toggle microphone mute.
voice.clear() Clear the accumulated transcript.
voice.update(options) Reconnect when connection identity or tuning options change.
voice.destroy() Remove listeners and disconnect the underlying VoiceClient.

Helper Exports

import {
    Agent,
    AgentChat,
    getAgentMessages,
    getToolApproval,
    getToolCallId,
    getToolInput,
    getToolOutput,
    getToolPartState
} from 'svelte-agents';
Export Description
Agent Svelte class equivalent of useAgent.
AgentChat Svelte class equivalent of useAgentChat.
getAgentMessages(...) Fetch persisted chat messages from an agent.
getToolPartState(part) Normalize AI SDK tool part states for UI rendering.
getToolCallId(part) Read a tool call id from a tool part.
getToolInput(part) Read tool input from a tool part.
getToolOutput(part) Read tool output from a tool part.
getToolApproval(part) Read tool approval metadata from a tool part.
extractClientToolSchemas(...) Deprecated React-parity helper.
detectToolsRequiringConfirmation(...) Deprecated React-parity helper.

Notes

  • This package is for Svelte 5.
  • The server-side runtime remains Cloudflare Agents. Use Cloudflare's docs for routeAgentRequest, AIChatAgent, callable methods, routing, and Workers deployment.
  • The client API is intentionally class-based so it can be used naturally in Svelte modules and components while preserving React hook parity.
  • Agent and AgentChat use Svelte runes internally, so properties like agent.state, agent.name, chat.messages, and chat.isStreaming update reactively in Svelte templates.

Top categories

Loading Svelte Themes