Web Framework Integration
Reactive Agents includes first-class support for streaming agent output into React, Vue, and Svelte applications. The pattern is consistent across frameworks:
- Server — A route handler calls
AgentStream.toSSE()and returns a standardResponse - Client — A hook/composable/store consumes the SSE stream and exposes reactive state
Server Setup
Section titled “Server Setup”The server-side is identical regardless of which client framework you use. AgentStream.toSSE() returns a standard Web API Response, making it compatible with any framework that accepts one.
Next.js App Router
Section titled “Next.js App Router”import { ReactiveAgents, AgentStream } from "reactive-agents";
export async function POST(req: Request) { const { prompt } = await req.json();
const agent = await ReactiveAgents.create() .withProvider("anthropic") .withReasoning() .withTools() .build();
return AgentStream.toSSE(agent.runStream(prompt));}SvelteKit
Section titled “SvelteKit”import { ReactiveAgents, AgentStream } from "reactive-agents";import type { RequestHandler } from "./$types";
export const POST: RequestHandler = async ({ request }) => { const { prompt } = await request.json();
const agent = await ReactiveAgents.create() .withProvider("anthropic") .withTools() .build();
return AgentStream.toSSE(agent.runStream(prompt));};Nuxt / H3
Section titled “Nuxt / H3”import { ReactiveAgents, AgentStream } from "reactive-agents";
export default defineEventHandler(async (event) => { const { prompt } = await readBody(event);
const agent = await ReactiveAgents.create() .withProvider("anthropic") .withTools() .build();
// Return the Web API Response directly — h3 handles it return AgentStream.toSSE(agent.runStream(prompt));});Bun.serve / Hono / Fastify
Section titled “Bun.serve / Hono / Fastify”// Bun.serveBun.serve({ port: 3000, async fetch(req) { if (req.method === "POST" && new URL(req.url).pathname === "/agent") { const { prompt } = await req.json(); const agent = await ReactiveAgents.create().withProvider("anthropic").withTools().build(); return AgentStream.toSSE(agent.runStream(prompt)); } return new Response("Not found", { status: 404 }); },});Install the package:
bun add @reactive-agents/reactuseAgentStream — Token-by-token streaming
Section titled “useAgentStream — Token-by-token streaming”import { useAgentStream } from "@reactive-agents/react";
function Chat() { const { text, status, error, run, cancel } = useAgentStream("/api/agent");
return ( <div> <button onClick={() => run("Research the latest AI agent frameworks")} disabled={status === "streaming"} > {status === "streaming" ? "Thinking..." : "Ask"} </button>
{status === "streaming" && ( <button onClick={cancel}>Stop</button> )}
<p style={{ whiteSpace: "pre-wrap" }}>{text}</p>
{status === "error" && <p style={{ color: "red" }}>{error}</p>} </div> );}useAgentStream return values:
| Property | Type | Description |
|---|---|---|
text | string | Accumulated output (grows as tokens arrive) |
status | "idle" | "streaming" | "completed" | "error" | Current execution state |
output | string | null | Full output when status === "completed" |
events | AgentStreamEvent[] | All raw events received since last run() |
error | string | null | Error message when status === "error" |
run | (prompt: string, body?) => void | Start a stream; cancels any active stream |
cancel | () => void | Cancel the active stream |
useAgent — One-shot (no streaming)
Section titled “useAgent — One-shot (no streaming)”import { useAgent } from "@reactive-agents/react";
function Summary({ text }: { text: string }) { const { output, loading, error, run } = useAgent("/api/agent");
return ( <div> <button onClick={() => run(`Summarize: ${text}`)} disabled={loading}> {loading ? "Summarizing..." : "Summarize"} </button> {output && <p>{output}</p>} {error && <p style={{ color: "red" }}>{error}</p>} </div> );}With custom headers or auth
Section titled “With custom headers or auth”const { text, run } = useAgentStream("/api/agent", { headers: { Authorization: `Bearer ${token}`, "X-Session-Id": sessionId, },});Iteration progress bar
Section titled “Iteration progress bar”import { useAgentStream } from "@reactive-agents/react";
function AgentWithProgress() { const { text, events, status, run } = useAgentStream("/api/agent");
const progress = events.findLast((e) => e._tag === "IterationProgress") as | { iteration: number; maxIterations: number } | undefined;
return ( <div> <button onClick={() => run("Research TypeScript 5.x features")}>Run</button>
{progress && ( <progress value={progress.iteration} max={progress.maxIterations} /> )}
<pre>{text}</pre> </div> );}Install the package:
bun add @reactive-agents/vueuseAgentStream
Section titled “useAgentStream”<script setup lang="ts">import { useAgentStream } from "@reactive-agents/vue";
const { text, status, error, run, cancel } = useAgentStream("/api/agent");</script>
<template> <div> <button @click="run('Research the latest AI agent frameworks')" :disabled="status === 'streaming'" > {{ status === 'streaming' ? 'Thinking...' : 'Ask' }} </button>
<button v-if="status === 'streaming'" @click="cancel">Stop</button>
<p style="white-space: pre-wrap">{{ text }}</p>
<p v-if="status === 'error'" style="color: red">{{ error }}</p> </div></template>All return values are Vue readonly refs — use them directly in templates or watch them:
const { text, status, output } = useAgentStream("/api/agent");
watch(status, (s) => { if (s === "completed") console.log("Done:", output.value);});useAgent — One-shot
Section titled “useAgent — One-shot”<script setup lang="ts">import { useAgent } from "@reactive-agents/vue";
const { output, loading, error, run } = useAgent("/api/agent");</script>
<template> <button @click="run('Summarize this article')" :disabled="loading"> {{ loading ? "Working..." : "Summarize" }} </button> <p v-if="output">{{ output }}</p></template>Svelte
Section titled “Svelte”Install the package:
bun add @reactive-agents/sveltecreateAgentStream
Section titled “createAgentStream”Returns a Svelte writable store — subscribe with $ prefix in templates:
<script lang="ts"> import { createAgentStream } from "@reactive-agents/svelte";
const agent = createAgentStream("/api/agent");</script>
<button on:click={() => agent.run("Research the latest AI agent frameworks")} disabled={$agent.status === "streaming"}> {$agent.status === "streaming" ? "Thinking..." : "Ask"}</button>
{#if $agent.status === "streaming"} <button on:click={agent.cancel}>Stop</button>{/if}
<p style="white-space: pre-wrap">{$agent.text}</p>
{#if $agent.status === "error"} <p style="color: red">{$agent.error}</p>{/if}Store state shape:
interface AgentStreamState { text: string; // Accumulated output status: "idle" | "streaming" | "completed" | "error"; output: string | null; error: string | null; events: AgentStreamEvent[];}createAgent — One-shot
Section titled “createAgent — One-shot”<script lang="ts"> import { createAgent } from "@reactive-agents/svelte";
const agent = createAgent("/api/agent");</script>
<button on:click={() => agent.run("Summarize this article")} disabled={$agent.loading}> {$agent.loading ? "Working..." : "Summarize"}</button>
{#if $agent.output} <p>{$agent.output}</p>{/if}Passing Extra Body Parameters
Section titled “Passing Extra Body Parameters”All hooks/stores accept an optional body object merged into the request body:
// Reactrun("Summarize this", { sessionId: "abc", temperature: 0.3 });
// Vuerun("Summarize this", { sessionId: "abc" });
// Svelteagent.run("Summarize this", { sessionId: "abc" });Update your server endpoint to read these:
export async function POST(req: Request) { const { prompt, sessionId, temperature } = await req.json();
const agent = await ReactiveAgents.create() .withProvider("anthropic") .withModel({ model: "claude-sonnet-4-20250514", temperature: temperature ?? 0.7 }) .build();
return AgentStream.toSSE(agent.runStream(prompt));}TypeScript — Event Types
Section titled “TypeScript — Event Types”All three packages export AgentStreamEvent for typed event handling:
import type { AgentStreamEvent } from "@reactive-agents/react"; // or vue / svelte
function handleEvent(event: AgentStreamEvent) { if (event._tag === "TextDelta") console.log(event.text); if (event._tag === "IterationProgress") console.log(event.iteration, event.maxIterations); if (event._tag === "StreamCompleted") console.log(event.output, event.metadata); if (event._tag === "StreamError") console.error(event.cause);}