e30e75b5d4
Changesets / Create Version PR (push) Has been cancelled
Deploy Shadcn Registry / Deploy Production (push) Has been cancelled
Template Metrics / LOC + Bundle Size (push) Has been cancelled
Code Quality / Oxlint + Oxfmt (push) Has been cancelled
Code Quality / Template Sync (push) Has been cancelled
Code Quality / Build Changed Packages (push) Has been cancelled
Code Quality / Test Changed Packages (push) Has been cancelled
Deploy Expo Example / Deploy Production (push) Has been cancelled
Deploy Ink Example / Deploy Production (push) Has been cancelled
Python Tests / pytest (assistant-stream, 3.10) (push) Has been cancelled
Python Tests / pytest (assistant-stream, 3.12) (push) Has been cancelled
Python Tests / pytest (assistant-ui-sync-server-api, 3.10) (push) Has been cancelled
Python Tests / pytest (assistant-ui-sync-server-api, 3.12) (push) Has been cancelled
101 lines
3.8 KiB
Markdown
101 lines
3.8 KiB
Markdown
# @assistant-ui/next
|
|
|
|
Next.js integration for assistant-ui: the `withAui()` config wrapper and the
|
|
compiler for the `"use generative"` directive. Colocate a tool's **schema**,
|
|
**server-only `execute`**, and **client-only `render`** in one file; the compiler
|
|
emits a different module per build target so each side only loads what it needs.
|
|
|
|
See [SPEC.md](./SPEC.md) for the full design.
|
|
|
|
## Why
|
|
|
|
`"use client"` is whole-module, so it can't keep a tool's zod schema readable on
|
|
the server while keeping its `render` on the client. And a backend `execute`
|
|
holds secrets (DB handles, API keys) that must never reach the browser bundle.
|
|
`"use generative"` routes each property to the right place.
|
|
|
|
Every tool **must** declare an `execute`, and you wrap the default export in
|
|
`defineToolkit({ ... })` (both are enforced — the compiler errors otherwise). You
|
|
don't declare a tool's kind: the compiler **infers** it from the `execute` and
|
|
writes a `type` field back into the output.
|
|
|
|
| how you author the `execute` | kind | where it runs |
|
|
| ----------------------------------------- | ---------- | ---------------------------- |
|
|
| `execute` with a `"use client"` directive | `frontend` | client |
|
|
| `execute` (plain) | `backend` | server (`server-only` guard) |
|
|
| `execute: humanTool()` | `human` | — (the UI supplies a result) |
|
|
|
|
A plain `execute` is server-only by default — you can only run one in the browser
|
|
by opting in with `"use client"`, so secrets can't leak by omission.
|
|
|
|
## Authoring
|
|
|
|
```tsx
|
|
"use generative";
|
|
import { z } from "zod";
|
|
import { defineToolkit } from "@assistant-ui/react";
|
|
import { db } from "@/db"; // server-only
|
|
import { Chart } from "@/ui/chart"; // client-only
|
|
|
|
export default defineToolkit({
|
|
weather: {
|
|
description: "Show the weather for a city.",
|
|
parameters: z.object({ city: z.string() }),
|
|
execute: async ({ city }) => db.weather.get(city), // backend → stays on the server
|
|
render: (props) => <Chart data={props} />, // stays on the client
|
|
},
|
|
});
|
|
```
|
|
|
|
The server build keeps `parameters` + `execute` (guarded by `import
|
|
"server-only"`, tagged `type: "backend"`) and drops `render` and `@/ui/chart`.
|
|
The client build keeps `parameters` + `render` (under `"use client"`) and drops
|
|
`execute` and `@/db`. A `frontend` tool marks its `execute` with `"use client"`:
|
|
|
|
```tsx
|
|
execute: async ({ city }) => {
|
|
"use client";
|
|
return navigator.geolocation /* … runs in the browser, kept client-side */;
|
|
},
|
|
```
|
|
|
|
## Wiring into Next.js
|
|
|
|
Wrap your config. Detection is by the `"use generative"` directive — there is **no
|
|
filename convention**; modules without the directive pass through untouched.
|
|
|
|
```ts
|
|
// next.config.ts
|
|
import { withAui } from "@assistant-ui/next";
|
|
|
|
export default withAui({
|
|
/* your Next config */
|
|
});
|
|
```
|
|
|
|
`withAui` applies the loader to your TS/TSX. To limit how many files it
|
|
scans, narrow the globs: `withAui(config, { rules: ["*.generative.tsx"] })`.
|
|
|
|
Import the module **bare** from both sides — the loader rewrites it into a facade
|
|
that resolves to the right build per layer (no query, no per-file config):
|
|
|
|
```tsx
|
|
// a client component → resolves to the client build (schema + render)
|
|
import toolkit from "@/lib/chat.generative";
|
|
|
|
// a route handler (react-server layer) → resolves to the server build (schema + execute)
|
|
import toolkit from "@/lib/chat.generative";
|
|
```
|
|
|
|
With the AI SDK, convert the server build to a `ToolSet` (see
|
|
`AISDKToolkit` in `@assistant-ui/react-ai-sdk`).
|
|
|
|
> **Validated on Next 16.2.6 (Turbopack).** Turbopack honors the loader-emitted
|
|
> `"use client"`, but compiles one output per resource path — so the server build
|
|
> is selected by its own `?generative-env=server` query rather than by build layer.
|
|
> Clear `.next` after changing the loader (Turbopack caches loader output).
|
|
|
|
## License
|
|
|
|
MIT
|