# `"use generative"` — specification

## Problem

A tool has three regions of code with three different deployment targets:

| region                      | server (registration + agent loop) | client (browser) |
| --------------------------- | ---------------------------------- | ---------------- |
| `description` / `parameters`| needed (→ LLM, parse)              | needed (→ parse) |
| `render`                    | **must not load** (React/CSS/DOM)  | needed           |
| `execute`                   | depends on kind (see routing)      | depends on kind  |

We want to **colocate all three in one source file** for DX, but keep `render`'s
client deps out of the server bundle and — more importantly — keep a backend
`execute`'s server deps (DB handles, API keys, server SDKs) out of the **client**
bundle. The second direction is a *security* boundary, not just bundle hygiene.

`"use client"` cannot express this: it is whole-module, so a `"use client"`
generative module would also turn `parameters` into a client reference on the server,
making the zod schema unreadable server-side. We need **sub-module, per-property**
routing. That is what the `"use generative"` directive provides.

## The directive

A module opts in with a leading directive and a single default export wrapped in
`defineToolkit`:

```tsx
"use generative";
import { z } from "zod";
import { defineToolkit } from "@assistant-ui/react";
import { db } from "@/db"; // server-only dependency
import { Chart } from "@/ui/chart"; // client-only dependency

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 (server-only)
    render: (props) => <Chart data={props} />, // client-only
  },
});
```

The file is imported from both server and client code; the compiler emits a
different module per build target so each side only loads what it needs.

## Routing (by inferred kind)

A tool's kind is **not authored** — declaring a `type` field is a type error. The
compiler infers it from the `execute` and writes the resolved `type` back into
each emitted tool object (so the runtime keeps it):

| how it's authored                         | inferred kind | `render` | `execute`                        |
| ----------------------------------------- | ------------- | -------- | -------------------------------- |
| `execute` with a `"use client"` directive | `frontend`    | client   | **client** (bundled with render) |
| `execute` (plain)                         | `backend`     | client   | **server** (`server-only` leaf)  |
| `execute: humanTool()`                    | `human`       | client   | — (dropped; the UI resolves it)  |

Consequences:

- For `frontend` entries the server keeps **schema only** — render *and* execute
  are client concerns.
- `backend` is the only kind that produces the server-only secrets boundary; its
  `execute` leaf imports `server-only`, so any routing mistake that pulls it into
  the client build fails the build instead of leaking secrets.
- **Server-by-default is the safe default:** a plain `execute` stays server-only,
  so a forgotten marker can't leak — you opt *into* the client with `"use client"`.
  A frontend `execute`'s `"use client"` is stripped from the output (the module
  already carries it).

## Compile targets

The compiler produces two self-contained rewrites of the source, selected by the
bundler per build layer. Each rewrite keeps only the relevant regions and prunes
the imports that became unused, so a dropped region's dependencies disappear.

### `client` target

- Keep `description`, `parameters`, `render`, and `execute` of `frontend` tools.
- Drop `execute` of `backend` tools (and its now-unused imports).
- Prepend `"use client"` when any `render` remains.

### `server` target

- Keep `description`, `parameters`, and `execute` of `backend` tools.
- Drop every `render` (and its now-unused imports).
- Drop `execute` of `frontend` tools.
- Prepend `import "server-only"` when any backend `execute` remains.

The `"use generative"` directive is stripped from both outputs.

## Bundler integration

Wrap the Next config with `withAui` from `@assistant-ui/next`
(no filename convention — modules are matched by the `"use generative"` directive,
and the loader passes non-generative files through untouched). It applies `./loader`,
a webpack/Turbopack loader.

The loader rewrites a **bare** generative import into a facade that delegates the
build choice to a `react-server`-conditioned package subpath, so a single import
resolves to the right build per layer — no query, no per-app config (see
DESIGN.md for the mechanism):

- route handler / RSC (`react-server` ON) → **server build** (schema + `execute`,
  guarded by `server-only`)
- client component, SSR + browser (`react-server` OFF) → **client build**
  (schema + `render`)

The concrete compile is keyed off an **internal** `?generative-env=server|client`
query the facade generates — it is never authored by consumers, so no ambient
module declaration is needed. (Clear `.next` after changing the loader —
Turbopack caches loader output aggressively.)

Why not infer the target from the build **layer** inside the loader? Turbopack
compiles one output per resource path and does not give a loader a per-layer
module instance — so the split must happen at resolve time (the `react-server`
export condition), which is exactly what the facade routes through.

## Consumption

Both sides import the module **bare**; the facade resolves each to the right build:

- **server:** import `./x.generative` in a route handler — it resolves to the
  server build (schema + `execute`). With the AI SDK,
  `await new AISDKToolkit({ toolkit }).tools({ frontend })` from
  `@assistant-ui/react-ai-sdk` converts it into a `ToolSet` whose `execute` runs
  in the route, merging in the frontend-uploaded tools.
- **client:** import `./x.generative` in a client component — it resolves to the
  client build (schema + `render`) — and register its tool UI.

Neither side ships the other's code, and the schema is never re-uploaded from
the client per request — the server owns it.

## Authoring constraints (enforced, with errors)

1. A leading `"use generative"` directive.
2. A single `export default defineToolkit({ ... })` (the wrapper is required;
   optionally inside `satisfies` / `as`). No other exports.
3. Every tool must declare an `execute`. Its form determines the kind: `humanTool()`
   → human; a leading `"use client"` directive → frontend (needs a block body,
   not an expression body); otherwise backend. `type` is never authored.
4. `render` / `execute` must be inline functions that close over **module
   imports only**, so they can be routed/pruned without dragging local scope.

## Known limitations (v1)

- Bare side-effect imports (e.g. `import "./styles.css"`) cannot be attributed to
  a region by reference analysis, so they are left untouched in both targets.
- Output preserves TS/JSX; the loader must run before the bundler's TS/JSX pass
  (the default in Next).
- Turbopack honors a loader-emitted `"use client"` directive (validated on Next
  16.2.6), but does not give a loader per-layer module instances — hence the
  `?generative-env=server` query rather than layer inference. Clear `.next` after
  changing the loader.
