npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

svelte-agents

v0.0.3

Published

Native Svelte 5 client bindings for the Cloudflare Agents SDK.

Readme

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

npm install 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.