Quickstart
For a SaaS engineer adding streaming chat. Three files, one env var, one minute.
If you're adding a streaming chat surface to an existing app — Next.js,
Astro, SvelteKit, Remix, Vite, Workers, Hono, plain Node — this is the
shortest path. It's the same wire-level surface
Getting started covers; this page goes deeper
on the env-var detection, the error contract, and what useChat
actually gives you.
One install, one env var, three files
npm install @pleach/coreSet one provider env var:
# .env (or your platform's env config)
ANTHROPIC_API_KEY=sk-ant-...// app/api/chat/route.ts
import { createPleachRoute } from "@pleach/core/quickstart";
export const POST = createPleachRoute();// app/page.tsx
"use client";
import { ChatBox } from "@pleach/core/quickstart";
export default function Home() {
return <ChatBox apiUrl="/api/chat" />;
}Run npm run dev. You have a streaming chat against Anthropic.
.env setup
The runtime checks env vars in priority order. The first one with a
non-empty value wins. Default order is alphabetical — not
preference-biased — so ANTHROPIC_API_KEY wins over
OPENROUTER_API_KEY when both are set. This behavior is
regression-tested at
packages/core/test/quickstart/providerDetection.test.mjs.
| Provider | Env var |
|---|---|
| Anthropic | ANTHROPIC_API_KEY |
| DeepSeek | DEEPSEEK_API_KEY |
GOOGLE_GENERATIVE_AI_API_KEY (or legacy GOOGLE_API_KEY) | |
| Mistral | MISTRAL_API_KEY |
| Moonshot | MOONSHOT_API_KEY |
| OpenAI | OPENAI_API_KEY |
| OpenRouter | OPENROUTER_API_KEY |
The seven-provider surface mirrors the ProviderFamily set exported
from @pleach/core/modelfamily plus OpenRouter as a gateway
transport. The (family × callClass) resolution matrix itself is
host-supplied — reached through the AgentAdapter.resolveModel<C>()
seam, not shipped in the package. See
Provider detection
for the full resolution matrix and how to override priority.
What you get
- Streaming response —
<ChatBox />renders tokens as they arrive via theuseChathook, which speaks Pleach'sStreamEventdiscriminated union directly. - Multi-turn memory —
useChatowns asessionIdand reuses it across calls. Each user message appends to the same session history. bot.reset()equivalent — in the React surface, this isclearMessages()on theuseChatreturn; on the recipes path (below), it'sbot.reset()orbot.newSession()on theChatbot.ChatStreamErrorrejection — when the underlying stream emits{ type: "error" }or the generator throws, the failure surfaces to the caller. The original cause is preserved onerror.cause; any stream-emittedcodeis preserved onerror.code. The factory contract is atpackages/recipes/src/chatbot.ts.
Troubleshooting
"No provider API key found in environment." (HTTP 503)
The route handler fails loudly when no provider env var is set rather
than silently falling back to a stub. Surface the 503 in your client;
it's the signal that ANTHROPIC_API_KEY (or your equivalent) didn't
reach the runtime.
The error payload is:
{
"error": "no_provider_detected",
"detail": "No provider API key found in environment. Set ANTHROPIC_API_KEY, OPENAI_API_KEY, OPENROUTER_API_KEY, or pass `provider` to createPleachRoute()."
}Fix: set one of the env vars from the table above, or pass
provider: "anthropic" explicitly to createPleachRoute({ provider }).
For keyless local dev — no env var, no 503 — pass
createPleachRoute({ demo: process.env.NODE_ENV !== "production" }).
With no key it drives a real graph turn over canned model text and
writes real audit rows (response header x-pleach-mode: demo); a
resolved key always wins, and it stays off in a production build.
It's the mode npx pleach init scaffolds, behind an /audit view.
localStorage is not defined
If you see this from the runtime in an SSR / Node context (server
route, edge function, test harness), it was resolved 2026-06-15 in
fix(core): generateClientId guards window.localStorage consistently (jsdom + SSR safe). The fix lives at
packages/core/src/utils/uuid.ts — generateClientId() now checks
typeof window === "undefined" || !window.localStorage before
attempting any read.
Upgrade @pleach/core to a version published after 2026-06-15.
ChatStreamError rejection
Chatbot.ask() (on the recipes path) rejects with ChatStreamError
when the underlying stream fails. The original failure is preserved:
try {
await bot.ask("hello");
} catch (err) {
if (err instanceof ChatStreamError) {
console.error("stream failed", err.message);
console.error("root cause:", err.cause);
console.error("stream code:", err.code);
}
}Common root causes: provider API key invalid (check .env),
provider rate limit hit, request aborted upstream. Inspect
err.cause for the provider-level error.
Alternative: @pleach/recipes/chatbot (no route handler)
If you don't need an HTTP boundary — for example you're building a CLI, a Node script, or a server-internal helper — reach for the recipes path:
npm install @pleach/recipes @pleach/coreimport { simpleChatbot } from "@pleach/recipes/chatbot";
import { setOrchestratorAdapterCtor } from "@pleach/core/runtime";
// At app boot: register your provider adapter ctor once.
setOrchestratorAdapterCtor(MyOrchestratorAdapter);
const bot = simpleChatbot({
systemPrompt: "You are a helpful assistant.",
orchestratorConfig: {
provider: "anthropic",
model: "claude-sonnet-4",
apiKey: process.env.ANTHROPIC_API_KEY!,
},
});
console.log(await bot.ask("hello"));
console.log(await bot.ask("what did I just say?")); // same session
await bot.reset();
console.log(await bot.ask("fresh conversation")); // new sessionHonest scope-limit: unlike the route handler, simpleChatbot does
NOT auto-detect provider env vars. You supply the provider explicitly
via orchestratorConfig and register the adapter ctor at app boot.
When orchestratorConfig is omitted, ask() resolves to the
substrate placeholder string
("This is a placeholder response from the harness runtime.") — by
design, so consumers without a wired provider see something
deterministic rather than a crash.
The bot.runtime field is the underlying SessionRuntime. Use it
directly when you need plugins, checkpoint inspection, custom
observers, or anything below the ask() surface. See
Upgrading to @pleach/core.
Where to go next
Provider detection
Env-var resolution matrix, priority override, custom env-var aliases.
Upgrading to @pleach/core
When `simpleChatbot` isn't enough — plugins, custom tools, custom orchestratorConfig, direct SessionRuntime use.
Which SKU do I need?
14 published packages, but most projects need 3 or 4.
Recipes
End-to-end runnable patterns — Next.js chat, OTel, multi-tenant, compliance, RAG, subagent swarms.