Architecture
web-ai-sdk is intentionally narrow. This page explains how the packages are organized, which decisions live inside the SDK and which are left to consumer code, and how to reason about adding a new capability.
One package per browser capability
Section titled “One package per browser capability”Each @web-ai-sdk/* package wraps a single cohesive web capability relevant to in-browser AI:
| Package | Wraps |
|---|---|
@web-ai-sdk/prompt | LanguageModel |
@web-ai-sdk/webmcp | document.modelContext |
@web-ai-sdk/summarizer | Summarizer |
@web-ai-sdk/translator | Translator |
@web-ai-sdk/detector | LanguageDetector |
@web-ai-sdk/writer | Writer |
@web-ai-sdk/rewriter | Rewriter |
@web-ai-sdk/proofreader | Proofreader |
@web-ai-sdk/all | Meta-package; re-exports the wrappers above |
The capability can originate from an official Built-in AI API (LanguageModel, Summarizer, Translator, LanguageDetector, Writer, Rewriter, Proofreader), from an adjacent web standard (WebMCP today; WebGPU and WebNN are likely candidates as their on-device AI uses crystallize), or, rarely, from a zero-dependency SDK-authored primitive with no platform equivalent yet. The rules below are what define an SDK package, not the origin of the underlying capability.
The structural rule is mechanical: one package = one cohesive capability. If the description of a package needs the word “and” to be honest about its scope, it’s probably two packages.
This is forced by shape, not just taste. The specialized built-ins carry option surfaces (target language, tone, length, confidence thresholds, per-issue offsets) that a single text-model abstraction cannot express, and WebMCP is an agent surface rather than a model. One capability per package is the only honest boundary.
No SDK package imports from another published SDK package. Composition across capabilities (running a Prompt session over a tool registered with WebMCP, for example) is something you write in your application code.
Zero-dependency policy
Section titled “Zero-dependency policy”Every @web-ai-sdk/* package ships with no runtime dependencies and no peer dependencies. The one exception is react, which is declared as an optional peer for the /react subpath adapter only.
This is policy, not aspiration. The reasoning:
- The Web AI surface is still evolving. The SDK tracks a moving spec; we don’t want to also be tracking a moving dependency graph.
- A wrapper that introduces transitive dependencies imports their security surface, their breaking changes, and their abandonment risk. We don’t think a
LanguageModelwrapper should ever pull in a logging library or a polyfill. - Zero deps means a
pnpm add @web-ai-sdk/promptadds exactly one entry to your lockfile.
When something useful really does need a third-party runtime (say, an IndexedDB-backed memory store or a JSON-RPC transport for a remote MCP server), it doesn’t belong here.
Vanilla trunk, React adapter as a subpath
Section titled “Vanilla trunk, React adapter as a subpath”Every package has two entry points:
// Vanilla: TypeScript / DOM only, framework-agnostic.import { ask, createSession } from "@web-ai-sdk/prompt";
// React: small hook adapter that wraps the vanilla core.import { usePrompt, useSession } from "@web-ai-sdk/prompt/react";The vanilla trunk is the source of truth. The React adapter is a thin layer that wires the vanilla core into hook lifecycles. There is no standalone @web-ai-sdk/prompt-react package, and there won’t be. Splitting along a framework boundary would force two release cadences for one capability.
If a different framework adapter ever ships, it will follow the same shape: @web-ai-sdk/<pkg>/<framework> as a subpath, with the framework declared as an optional peer.
Ergonomic defaults vs. opinions
Section titled “Ergonomic defaults vs. opinions”The SDK is allowed to be ergonomic within a single capability. Examples that live inside @web-ai-sdk/prompt:
- Warm LRU session pool.
ask()reusesLanguageModel.create()results across same-shape callers. Cold start drops to sub-second. The pool size is fixed at a sensible default; if you need fine-grained eviction control, usecreateSession()and own the lifecycle yourself. - Optional result cache.
ask()accepts acacheoption that memoizes responses by(input, systemPrompt, samplingMode / temperature / topK). Off by default; pass"session"/"local"for the matching web-storage shortcut, or supply your own{ get, set }backend. - Delta-vs-cumulative stream normalization. Chrome emits delta chunks; some Edge backends emit cumulative buffers. The wrapper smooths this so
onUpdatealways sees the cumulative buffer andsendStreaming()always yields deltas.
These are ergonomic defaults, not opinions about how you build an app. They each:
- live entirely inside one capability,
- can be overridden, disabled, or replaced,
- and would otherwise be reimplemented by every consumer.
What you won’t find in the SDK is anything that bonds multiple capabilities together, walks the DOM, or needs a runtime dependency. Those concerns live in consumer code — anything you’d want to compose is yours to write.
Lineage: thin shims over platform primitives
Section titled “Lineage: thin shims over platform primitives”This shape isn’t novel, and that’s the point. The most durable JS utility libraries trend toward becoming thin layers over a platform primitive, then shrink as the platform absorbs their surface:
- lodash ceded most of its surface to native array and object methods.
- moment is superseded by the
Temporalproposal; date-fns is now reorienting to be “Temporal-first”: document whereTemporalreplaces a function outright, map the gaps it can’t cover, and ship the rest as functions that operate directly onTemporalobjects.
web-ai-sdk starts from that endpoint instead of migrating toward it. The Web AI APIs are the primitive; the SDK only owns the lifecycle and ergonomics the primitive leaves rough (feature detection, session reuse, stream smoothing, safe cleanup). As the APIs stabilize, the right outcome is for this layer to get thinner, not fatter. If a capability graduates to needing nothing but the raw API, the corresponding wrapper has done its job.
Summary
Section titled “Summary”The SDK in one line: one package per browser capability, zero runtime deps, lifecycle layer only.
| In scope for the SDK | Out of scope |
|---|---|
| Wraps one browser capability | Composes two or more capabilities |
| Lifecycle: feature detection, session cache, abort, error wrapping | Retries, history, rendering, persistence |
| Ergonomic defaults inside one capability (LRU cache, stream smoothing) | Walks or mutates the DOM beyond what the platform API does |
| Zero runtime dependencies | Anything that needs a third-party runtime dependency |
New web capabilities (Built-in AI, WebGPU, WebNN, or adjacent standards) land in the SDK as new packages. Reusable usage patterns that compose multiple capabilities are consumer-code territory.