Skip to content

useSession

Lifecycle-only React adapter for the chat-shaped createSession() primitive in @web-ai-sdk/prompt. Each useSession call owns one underlying LanguageModelInstance, with independent history, system prompt, sampling, and lifecycle. The hook handles feature detection, async create readiness, destroy-on-unmount, and recreate-on-options-change. It does not track response, history, or streaming status; iterate session.sendStreaming() yourself and keep UI state in your own component.

For ask-and-display flows (embeds, widgets, single-question UIs) prefer usePrompt; it uses isolated one-shot prompts and may keep a warm base session for same-shape calls.

import { useSession } from "@web-ai-sdk/prompt/react";
import { useState } from "react";
export function Chat({ persona }: { persona: string }) {
const { status, session } = useSession({
systemPrompt: persona,
samplingMode: "balanced",
});
const [response, setResponse] = useState("");
const [streaming, setStreaming] = useState(false);
if (status === "unavailable" || !session) return null;
const send = async (text: string) => {
setStreaming(true);
setResponse("");
try {
let buffer = "";
for await (const delta of session.sendStreaming(text)) {
buffer += delta;
setResponse(buffer);
}
} finally {
setStreaming(false);
}
};
return (
<div>
<p>{response}</p>
<form onSubmit={(e) => { e.preventDefault(); send("Hello"); }}>
<button type="submit" disabled={streaming}>Send</button>
<button type="button" onClick={() => session.abort()}>Stop</button>
</form>
</div>
);
}

session.sendStreaming(text) yields deltas (each chunk is the new text since the last yield). Accumulate into your own response state for a typewriter effect. The hook stays out of the streaming loop entirely.

Two useSession calls with identical options get two independent underlying instances. In a chat app with N agents in the same mode, each pane owns its own history, abort, and destroy lifecycle. Current Chrome / Edge builds may still serialize token generation FIFO across sessions because the underlying on-device model is single-instance.

function ChatList({ agents }: { agents: Agent[] }) {
return agents.map((agent) => <ChatPane key={agent.id} agent={agent} />);
}
function ChatPane({ agent }: { agent: Agent }) {
// Each ChatPane owns its own session even when systemPrompt /
// samplingMode are identical.
const { session } = useSession({
systemPrompt: agent.systemPrompt,
samplingMode: agent.samplingMode,
});
// …
}

Forking with clone() (agents / multi-task)

Section titled “Forking with clone() (agents / multi-task)”

session is the vanilla Session, so session.clone() is available in React too. For agents that run many tasks against one persona, keep the useSession base warm (system prompt only) and clone it per task so each run gets fresh history without re-parsing the system prompt. See Session resilience for the full rationale.

const { session } = useSession({ systemPrompt });
const runTask = async (input: string) => {
if (!session) return;
const turn = await session.clone(); // fresh history, base stays warm
try {
for await (const delta of turn.sendStreaming(input)) render(delta);
} finally {
turn.destroy();
}
};

Destroying a clone never affects the base session. The hook still owns the base session’s lifecycle (destroyed on unmount / option change); clones you create are yours to destroy.

interface UseSessionReturn {
status: "loading" | "ready" | "unavailable";
error: Error | null;
session: Session | null; // null until status === "ready"
}
  • loading: the underlying LanguageModel.create() is in flight. session is null until creation succeeds.
  • ready: session is non-null. Iterate session.sendStreaming(...), call session.send(...), session.abort(), or session.destroy() (the hook also destroys on unmount).
  • unavailable: API missing, enabled: false, or native creation failed. Render a fallback; creation failures are exposed on error.

The hook waits for native creation before reporting "ready". If LanguageModel.create() rejects, the hook destroys the wrapper, reports "unavailable", and stores the creation error in error.

The hook recreates the underlying instance whenever a primitive option changes (systemPrompt, samplingMode, temperature, topK, language, enabled). Object options (expectedInputs, expectedOutputs, createOptions) participate in the dependency check by reference; memoize them or accept the recreate cost.

const expected = useMemo<LanguageModelExpectedInput[]>(
() => [{ type: "text", languages: ["en"] }],
[],
);
const { session } = useSession({ systemPrompt, expectedInputs: expected });

Pass enabled: false to skip session creation entirely (status stays "unavailable"). Flipping it back to true creates the session on the next render. Useful when the session depends on user opt-in.

Pass createOptions.initialPrompts to restore a prior conversation:

const { status, session } = useSession({
systemPrompt: "You are a helpful assistant.",
createOptions: {
initialPrompts: [
{ role: "system", content: "You are a helpful assistant." },
{ role: "user", content: "Earlier turn from storage." },
{ role: "assistant", content: "Earlier reply." },
],
},
});
if (status !== "ready" || !session) return null;

The seeded turns become real context for the model. UI history lives in your own component state.

import type { UseSessionOptions, UseSessionReturn, SessionStatus } from "@web-ai-sdk/prompt/react";
type SessionStatus = "loading" | "ready" | "unavailable";
interface UseSessionOptions extends CreateSessionOptions {
enabled?: boolean; // default: true; skip session creation when false
// Inherited from CreateSessionOptions:
systemPrompt?: string;
samplingMode?: "most-predictable" | "predictable" | "balanced" | "creative" | "most-creative";
temperature?: number;
topK?: number;
language?: string;
supportedLanguages?: readonly string[];
expectedInputs?: LanguageModelExpectedInput[];
expectedOutputs?: LanguageModelExpectedOutput[];
createOptions?: LanguageModelCreateOptions;
}
interface UseSessionReturn {
status: SessionStatus;
error: Error | null; // creation error, if any
session: Session | null; // null until status === "ready"
}
interface Session {
readonly destroyed: boolean;
readonly contextWindow?: number;
readonly contextUsage?: number;
send(input: string, options?: SessionSendOptions): Promise<string | null>;
sendStreaming(input: string, options?: SessionSendOptions): AsyncIterable<string>; // yields deltas, not cumulative
abort(): void;
clone(options?: { signal?: AbortSignal }): Promise<Session>;
onContextOverflow(listener: () => void): () => void;
destroy(): void;
}
declare const useSession: (options?: UseSessionOptions) => UseSessionReturn;

Source: packages/prompt/src/react/index.ts.