glove-react
React bindings for the Glove agent framework — hooks, components, and tools with colocated renderers.
Install
npm install glove-react glove-next
Requires react >= 18.0.0 as a peer dependency.
Quick start
1. Server route (Next.js)
import { createChatHandler } from "glove-next";
export const POST = createChatHandler({
provider: "anthropic",
model: "claude-sonnet-4-20250514",
});
2. Define tools
import { GloveClient, defineTool } from "glove-react";
import type { ToolConfig } from "glove-react";
import { z } from "zod";
const inputSchema = z.object({
question: z.string().describe("The question to display"),
options: z.array(z.object({
label: z.string().describe("Display text"),
value: z.string().describe("Value returned when selected"),
})),
});
const askPreferenceTool = defineTool({
name: "ask_preference",
description: "Present options for the user to choose from.",
inputSchema,
displayPropsSchema: inputSchema,
resolveSchema: z.string(),
displayStrategy: "hide-on-complete",
async do(input, display) {
const selected = await display.pushAndWait(input);
return {
status: "success" as const,
data: `User selected: ${selected}`,
renderData: { question: input.question, selected },
};
},
render({ props, resolve }) {
return (
<div>
<p>{props.question}</p>
{props.options.map(opt => (
<button key={opt.value} onClick={() => resolve(opt.value)}>
{opt.label}
</button>
))}
</div>
);
},
renderResult({ data }) {
const { question, selected } = data as { question: string; selected: string };
return <div><p>{question}</p><span>Selected: {selected}</span></div>;
},
});
const getDateTool: ToolConfig = {
name: "get_date",
description: "Get today's date",
inputSchema: z.object({}),
async do() { return { status: "success", data: new Date().toLocaleDateString() }; },
};
export const gloveClient = new GloveClient({
endpoint: "/api/chat",
systemPrompt: "You are a helpful assistant.",
tools: [askPreferenceTool, getDateTool],
});
3. Provider + UI
"use client";
import { GloveProvider } from "glove-react";
import { gloveClient } from "@/lib/glove";
export function Providers({ children }: { children: React.ReactNode }) {
return <GloveProvider client={gloveClient}>{children}</GloveProvider>;
}
"use client";
import { useGlove, Render } from "glove-react";
export default function Chat() {
const glove = useGlove();
return (
<Render
glove={glove}
strategy="interleaved"
renderMessage={({ entry }) => (
<div><strong>{entry.kind === "user" ? "You" : "AI"}:</strong> {entry.text}</div>
)}
renderStreaming={({ text }) => <div style={{ opacity: 0.7 }}>{text}</div>}
/>
);
}
Key exports
Components & hooks
GloveProvider — Context provider wrapping your app
useGlove(config?) — Main hook returning timeline, streamingText, busy, slots, tasks, stats, sessionReady, sessionId, sendMessage, abort, renderSlot, renderToolResult, resolveSlot, rejectSlot. Accepts an optional getSessionId async function to resolve the session ID at runtime (overrides the client-level one if set).
Render — Headless render component with automatic slot visibility, interleaving, and renderResult rendering
Tool helpers
defineTool(config) — Type-safe tool builder with colocated render and renderResult. Provides typed display props and resolve values.
ToolConfig — Raw tool interface for tools without display UI
Client
GloveClient — Configuration container. Pass endpoint (for server-side models via glove-next) or createModel (for client-side models). Optionally pass getSessionId to resolve the session ID asynchronously instead of providing one directly.
Adapters
MemoryStore — In-memory store for prototyping
createRemoteStore — Delegates store operations to your API endpoints
createRemoteModel — Custom model adapter with prompt and optional promptStream
createEndpointModel — SSE-based model compatible with glove-next handlers
parseSSEStream — Parse an SSE response stream into RemoteStreamEvent objects
Voice bindings
Voice hooks and components are exported from glove-react/voice:
useGloveVoice — Core voice hook (mode, transcript, start/stop/interrupt)
useGlovePTT — Push-to-talk with click-vs-hold detection, hotkey, min-duration
VoicePTTButton — Headless PTT button with render prop and ARIA attributes
Requires glove-voice as a peer dependency.
Async session ID
When your session ID comes from a backend (auth tokens, server-assigned IDs, etc.), use getSessionId instead of a static sessionId:
const client = new GloveClient({
endpoint: "/api/chat",
systemPrompt: "You are a helpful assistant.",
getSessionId: async () => {
const res = await fetch("/api/session");
const { sessionId } = await res.json();
return sessionId;
},
});
function Chat() {
const glove = useGlove({
getSessionId: async () => {
const res = await fetch("/api/session");
const { sessionId } = await res.json();
return sessionId;
},
});
if (!glove.sessionReady) return <div>Loading session...</div>;
console.log("Session:", glove.sessionId);
return <Render glove={glove} />;
}
When getSessionId is configured, the store is null until the ID resolves. The hook guards the build, hydration, and sendMessage flows against the null store, so consumers only need to check sessionReady before rendering. When no getSessionId is provided, behavior is unchanged -- sessionReady is always true and the session ID is either the provided sessionId or an auto-generated UUID.
Display strategies
"stay" (default) | Slot always visible |
"hide-on-complete" | Hidden when slot is resolved/rejected |
"hide-on-new" | Hidden when a newer slot from the same tool appears |
Documentation
License
MIT