@assistant-ui/next 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,100 @@
1
+ # @assistant-ui/next
2
+
3
+ Next.js integration for assistant-ui: the `withAui()` config wrapper and the
4
+ compiler for the `"use generative"` directive. Colocate a tool's **schema**,
5
+ **server-only `execute`**, and **client-only `render`** in one file; the compiler
6
+ emits a different module per build target so each side only loads what it needs.
7
+
8
+ See [SPEC.md](./SPEC.md) for the full design.
9
+
10
+ ## Why
11
+
12
+ `"use client"` is whole-module, so it can't keep a tool's zod schema readable on
13
+ the server while keeping its `render` on the client. And a backend `execute`
14
+ holds secrets (DB handles, API keys) that must never reach the browser bundle.
15
+ `"use generative"` routes each property to the right place.
16
+
17
+ Every tool **must** declare an `execute`, and you wrap the default export in
18
+ `defineToolkit({ ... })` (both are enforced — the compiler errors otherwise). You
19
+ don't declare a tool's kind: the compiler **infers** it from the `execute` and
20
+ writes a `type` field back into the output.
21
+
22
+ | how you author the `execute` | kind | where it runs |
23
+ | ----------------------------------------- | ---------- | ---------------------------- |
24
+ | `execute` with a `"use client"` directive | `frontend` | client |
25
+ | `execute` (plain) | `backend` | server (`server-only` guard) |
26
+ | `execute: hitl()` | `human` | — (the UI supplies a result) |
27
+
28
+ A plain `execute` is server-only by default — you can only run one in the browser
29
+ by opting in with `"use client"`, so secrets can't leak by omission.
30
+
31
+ ## Authoring
32
+
33
+ ```tsx
34
+ "use generative";
35
+ import { z } from "zod";
36
+ import { defineToolkit } from "@assistant-ui/react";
37
+ import { db } from "@/db"; // server-only
38
+ import { Chart } from "@/ui/chart"; // client-only
39
+
40
+ export default defineToolkit({
41
+ weather: {
42
+ description: "Show the weather for a city.",
43
+ parameters: z.object({ city: z.string() }),
44
+ execute: async ({ city }) => db.weather.get(city), // backend → stays on the server
45
+ render: (props) => <Chart data={props} />, // stays on the client
46
+ },
47
+ });
48
+ ```
49
+
50
+ The server build keeps `parameters` + `execute` (guarded by `import
51
+ "server-only"`, tagged `type: "backend"`) and drops `render` and `@/ui/chart`.
52
+ The client build keeps `parameters` + `render` (under `"use client"`) and drops
53
+ `execute` and `@/db`. A `frontend` tool marks its `execute` with `"use client"`:
54
+
55
+ ```tsx
56
+ execute: async ({ city }) => {
57
+ "use client";
58
+ return navigator.geolocation /* … runs in the browser, kept client-side */;
59
+ },
60
+ ```
61
+
62
+ ## Wiring into Next.js
63
+
64
+ Wrap your config. Detection is by the `"use generative"` directive — there is **no
65
+ filename convention**; modules without the directive pass through untouched.
66
+
67
+ ```ts
68
+ // next.config.ts
69
+ import { withAui } from "@assistant-ui/next";
70
+
71
+ export default withAui({
72
+ /* your Next config */
73
+ });
74
+ ```
75
+
76
+ `withAui` applies the loader to your TS/TSX. To limit how many files it
77
+ scans, narrow the globs: `withAui(config, { rules: ["*.generative.tsx"] })`.
78
+
79
+ Import the module **bare** from both sides — the loader rewrites it into a facade
80
+ that resolves to the right build per layer (no query, no per-file config):
81
+
82
+ ```tsx
83
+ // a client component → resolves to the client build (schema + render)
84
+ import toolkit from "@/lib/chat.generative";
85
+
86
+ // a route handler (react-server layer) → resolves to the server build (schema + execute)
87
+ import toolkit from "@/lib/chat.generative";
88
+ ```
89
+
90
+ With the AI SDK, convert the server build to a `ToolSet` (see
91
+ `generativeTools` in `@assistant-ui/react-ai-sdk`).
92
+
93
+ > **Validated on Next 16.2.6 (Turbopack).** Turbopack honors the loader-emitted
94
+ > `"use client"`, but compiles one output per resource path — so the server build
95
+ > is selected by its own `?generative-env=server` query rather than by build layer.
96
+ > Clear `.next` after changing the loader (Turbopack caches loader output).
97
+
98
+ ## License
99
+
100
+ MIT
package/SPEC.md ADDED
@@ -0,0 +1,154 @@
1
+ # `"use generative"` — specification
2
+
3
+ ## Problem
4
+
5
+ A tool has three regions of code with three different deployment targets:
6
+
7
+ | region | server (registration + agent loop) | client (browser) |
8
+ | --------------------------- | ---------------------------------- | ---------------- |
9
+ | `description` / `parameters`| needed (→ LLM, parse) | needed (→ parse) |
10
+ | `render` | **must not load** (React/CSS/DOM) | needed |
11
+ | `execute` | depends on kind (see routing) | depends on kind |
12
+
13
+ We want to **colocate all three in one source file** for DX, but keep `render`'s
14
+ client deps out of the server bundle and — more importantly — keep a backend
15
+ `execute`'s server deps (DB handles, API keys, server SDKs) out of the **client**
16
+ bundle. The second direction is a *security* boundary, not just bundle hygiene.
17
+
18
+ `"use client"` cannot express this: it is whole-module, so a `"use client"`
19
+ generative module would also turn `parameters` into a client reference on the server,
20
+ making the zod schema unreadable server-side. We need **sub-module, per-property**
21
+ routing. That is what the `"use generative"` directive provides.
22
+
23
+ ## The directive
24
+
25
+ A module opts in with a leading directive and a single default export wrapped in
26
+ `defineToolkit`:
27
+
28
+ ```tsx
29
+ "use generative";
30
+ import { z } from "zod";
31
+ import { defineToolkit } from "@assistant-ui/react";
32
+ import { db } from "@/db"; // server-only dependency
33
+ import { Chart } from "@/ui/chart"; // client-only dependency
34
+
35
+ export default defineToolkit({
36
+ weather: {
37
+ description: "Show the weather for a city.",
38
+ parameters: z.object({ city: z.string() }),
39
+ execute: async ({ city }) => db.weather.get(city), // backend (server-only)
40
+ render: (props) => <Chart data={props} />, // client-only
41
+ },
42
+ });
43
+ ```
44
+
45
+ The file is imported from both server and client code; the compiler emits a
46
+ different module per build target so each side only loads what it needs.
47
+
48
+ ## Routing (by inferred kind)
49
+
50
+ A tool's kind is **not authored** — declaring a `type` field is a type error. The
51
+ compiler infers it from the `execute` and writes the resolved `type` back into
52
+ each emitted tool object (so the runtime keeps it):
53
+
54
+ | how it's authored | inferred kind | `render` | `execute` |
55
+ | ----------------------------------------- | ------------- | -------- | -------------------------------- |
56
+ | `execute` with a `"use client"` directive | `frontend` | client | **client** (bundled with render) |
57
+ | `execute` (plain) | `backend` | client | **server** (`server-only` leaf) |
58
+ | `execute: hitl()` | `human` | client | — (dropped; the UI resolves it) |
59
+
60
+ Consequences:
61
+
62
+ - For `frontend` entries the server keeps **schema only** — render *and* execute
63
+ are client concerns.
64
+ - `backend` is the only kind that produces the server-only secrets boundary; its
65
+ `execute` leaf imports `server-only`, so any routing mistake that pulls it into
66
+ the client build fails the build instead of leaking secrets.
67
+ - **Server-by-default is the safe default:** a plain `execute` stays server-only,
68
+ so a forgotten marker can't leak — you opt *into* the client with `"use client"`.
69
+ A frontend `execute`'s `"use client"` is stripped from the output (the module
70
+ already carries it).
71
+
72
+ ## Compile targets
73
+
74
+ The compiler produces two self-contained rewrites of the source, selected by the
75
+ bundler per build layer. Each rewrite keeps only the relevant regions and prunes
76
+ the imports that became unused, so a dropped region's dependencies disappear.
77
+
78
+ ### `client` target
79
+
80
+ - Keep `description`, `parameters`, `render`, and `execute` of `frontend` tools.
81
+ - Drop `execute` of `backend` tools (and its now-unused imports).
82
+ - Prepend `"use client"` when any `render` remains.
83
+
84
+ ### `server` target
85
+
86
+ - Keep `description`, `parameters`, and `execute` of `backend` tools.
87
+ - Drop every `render` (and its now-unused imports).
88
+ - Drop `execute` of `frontend` tools.
89
+ - Prepend `import "server-only"` when any backend `execute` remains.
90
+
91
+ The `"use generative"` directive is stripped from both outputs.
92
+
93
+ ## Bundler integration
94
+
95
+ Wrap the Next config with `withAui` from `@assistant-ui/next`
96
+ (no filename convention — modules are matched by the `"use generative"` directive,
97
+ and the loader passes non-generative files through untouched). It applies `./loader`,
98
+ a webpack/Turbopack loader.
99
+
100
+ The loader rewrites a **bare** generative import into a facade that delegates the
101
+ build choice to a `react-server`-conditioned package subpath, so a single import
102
+ resolves to the right build per layer — no query, no per-app config (see
103
+ DESIGN.md for the mechanism):
104
+
105
+ - route handler / RSC (`react-server` ON) → **server build** (schema + `execute`,
106
+ guarded by `server-only`)
107
+ - client component, SSR + browser (`react-server` OFF) → **client build**
108
+ (schema + `render`)
109
+
110
+ The concrete compile is keyed off an **internal** `?generative-env=server|client`
111
+ query the facade generates — it is never authored by consumers, so no ambient
112
+ module declaration is needed. (Clear `.next` after changing the loader —
113
+ Turbopack caches loader output aggressively.)
114
+
115
+ Why not infer the target from the build **layer** inside the loader? Turbopack
116
+ compiles one output per resource path and does not give a loader a per-layer
117
+ module instance — so the split must happen at resolve time (the `react-server`
118
+ export condition), which is exactly what the facade routes through.
119
+
120
+ ## Consumption
121
+
122
+ Both sides import the module **bare**; the facade resolves each to the right build:
123
+
124
+ - **server:** import `./x.generative` in a route handler — it resolves to the
125
+ server build (schema + `execute`). With the AI SDK, `generativeTools({ toolkit,
126
+ frontendTools })` from `@assistant-ui/react-ai-sdk` converts it into a `ToolSet`
127
+ whose `execute` runs in the route, merging in the frontend-uploaded tools.
128
+ - **client:** import `./x.generative` in a client component — it resolves to the
129
+ client build (schema + `render`) — and register its tool UI.
130
+
131
+ Neither side ships the other's code, and the schema is never re-uploaded from
132
+ the client per request — the server owns it.
133
+
134
+ ## Authoring constraints (enforced, with errors)
135
+
136
+ 1. A leading `"use generative"` directive.
137
+ 2. A single `export default defineToolkit({ ... })` (the wrapper is required;
138
+ optionally inside `satisfies` / `as`). No other exports.
139
+ 3. Every tool must declare an `execute`. Its form determines the kind: `hitl()`
140
+ → human; a leading `"use client"` directive → frontend (needs a block body,
141
+ not an expression body); otherwise backend. `type` is never authored.
142
+ 4. `render` / `execute` must be inline functions that close over **module
143
+ imports only**, so they can be routed/pruned without dragging local scope.
144
+
145
+ ## Known limitations (v1)
146
+
147
+ - Bare side-effect imports (e.g. `import "./styles.css"`) cannot be attributed to
148
+ a region by reference analysis, so they are left untouched in both targets.
149
+ - Output preserves TS/JSX; the loader must run before the bundler's TS/JSX pass
150
+ (the default in Next).
151
+ - Turbopack honors a loader-emitted `"use client"` directive (validated on Next
152
+ 16.2.6), but does not give a loader per-layer module instances — hence the
153
+ `?generative-env=server` query rather than layer inference. Clear `.next` after
154
+ changing the loader.
@@ -0,0 +1 @@
1
+ export { };
@@ -0,0 +1,5 @@
1
+ //#region src/bundler-redirect.client.ts
2
+ throw new Error("@assistant-ui/next/bundler-redirect is internal; import it through the @assistant-ui/next loader.");
3
+ //#endregion
4
+
5
+ //# sourceMappingURL=bundler-redirect.client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bundler-redirect.client.js","names":[],"sources":["../src/bundler-redirect.client.ts"],"sourcesContent":["// Internal default-condition indirection target (SSR + browser) — always\n// replaced by the @assistant-ui/next loader, which re-exports the module's\n// client build. Reaching this means the loader wasn't applied (see DESIGN.md).\nthrow new Error(\n \"@assistant-ui/next/bundler-redirect is internal; import it through the \" +\n \"@assistant-ui/next loader.\",\n);\n"],"mappings":";AAGA,MAAM,IAAI,MACR,mGAEF"}
@@ -0,0 +1 @@
1
+ export { };
@@ -0,0 +1,5 @@
1
+ //#region src/bundler-redirect.server.ts
2
+ throw new Error("@assistant-ui/next/bundler-redirect is internal; import it through the @assistant-ui/next loader.");
3
+ //#endregion
4
+
5
+ //# sourceMappingURL=bundler-redirect.server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bundler-redirect.server.js","names":[],"sources":["../src/bundler-redirect.server.ts"],"sourcesContent":["// Internal `react-server` indirection target — always replaced by the\n// @assistant-ui/next loader, which re-exports the module's server build.\n// Reaching this means the loader wasn't applied (see DESIGN.md).\nthrow new Error(\n \"@assistant-ui/next/bundler-redirect is internal; import it through the \" +\n \"@assistant-ui/next loader.\",\n);\n"],"mappings":";AAGA,MAAM,IAAI,MACR,mGAEF"}
@@ -0,0 +1,19 @@
1
+ import { Toolkit, ToolkitDeclaration } from "@assistant-ui/core/react";
2
+
3
+ //#region src/define-toolkit.d.ts
4
+ /**
5
+ * Authoring helper for a `"use generative"` toolkit. Accepts the permissive
6
+ * {@link ToolkitDeclaration} (a `backend` tool may carry its server `execute`)
7
+ * and types the result as the canonical {@link Toolkit}.
8
+ *
9
+ * It has **no runtime implementation**. The `@assistant-ui/next` compiler strips
10
+ * the `defineToolkit(...)` wrapper (and its import) per build, so a correctly
11
+ * compiled `export default defineToolkit({...})` never calls this. If it *does*
12
+ * run, the module was not compiled by the use-generative loader — e.g.
13
+ * `defineToolkit` used outside a `"use generative"` file — which would ship a
14
+ * backend `execute` to the client. So it throws instead of silently leaking.
15
+ */
16
+ declare function defineToolkit(_declaration: ToolkitDeclaration): Toolkit;
17
+ //#endregion
18
+ export { defineToolkit };
19
+ //# sourceMappingURL=define-toolkit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"define-toolkit.d.ts","names":[],"sources":["../src/define-toolkit.ts"],"mappings":";;;;;AAcA;;;;;;;;AAAwE;;iBAAxD,aAAA,CAAc,YAAA,EAAc,kBAAA,GAAqB,OAAO"}
@@ -0,0 +1,20 @@
1
+ //#region src/define-toolkit.ts
2
+ /**
3
+ * Authoring helper for a `"use generative"` toolkit. Accepts the permissive
4
+ * {@link ToolkitDeclaration} (a `backend` tool may carry its server `execute`)
5
+ * and types the result as the canonical {@link Toolkit}.
6
+ *
7
+ * It has **no runtime implementation**. The `@assistant-ui/next` compiler strips
8
+ * the `defineToolkit(...)` wrapper (and its import) per build, so a correctly
9
+ * compiled `export default defineToolkit({...})` never calls this. If it *does*
10
+ * run, the module was not compiled by the use-generative loader — e.g.
11
+ * `defineToolkit` used outside a `"use generative"` file — which would ship a
12
+ * backend `execute` to the client. So it throws instead of silently leaking.
13
+ */
14
+ function defineToolkit(_declaration) {
15
+ throw new Error("[assistant-ui/next] defineToolkit() has no runtime implementation — it is stripped at build time by the use-generative compiler. Reaching it means this module was not compiled (e.g. defineToolkit used outside a \"use generative\" file). Add the directive, or do not use defineToolkit here.");
16
+ }
17
+ //#endregion
18
+ export { defineToolkit };
19
+
20
+ //# sourceMappingURL=define-toolkit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"define-toolkit.js","names":[],"sources":["../src/define-toolkit.ts"],"sourcesContent":["import type { Toolkit, ToolkitDeclaration } from \"@assistant-ui/core/react\";\n\n/**\n * Authoring helper for a `\"use generative\"` toolkit. Accepts the permissive\n * {@link ToolkitDeclaration} (a `backend` tool may carry its server `execute`)\n * and types the result as the canonical {@link Toolkit}.\n *\n * It has **no runtime implementation**. The `@assistant-ui/next` compiler strips\n * the `defineToolkit(...)` wrapper (and its import) per build, so a correctly\n * compiled `export default defineToolkit({...})` never calls this. If it *does*\n * run, the module was not compiled by the use-generative loader — e.g.\n * `defineToolkit` used outside a `\"use generative\"` file — which would ship a\n * backend `execute` to the client. So it throws instead of silently leaking.\n */\nexport function defineToolkit(_declaration: ToolkitDeclaration): Toolkit {\n throw new Error(\n \"[assistant-ui/next] defineToolkit() has no runtime implementation — it is \" +\n \"stripped at build time by the use-generative compiler. Reaching it means \" +\n 'this module was not compiled (e.g. defineToolkit used outside a \"use ' +\n 'generative\" file). Add the directive, or do not use defineToolkit here.',\n );\n}\n"],"mappings":";;;;;;;;;;;;;AAcA,SAAgB,cAAc,cAA2C;CACvE,MAAM,IAAI,MACR,mSAIF;AACF"}
package/dist/hitl.d.ts ADDED
@@ -0,0 +1,18 @@
1
+ //#region src/hitl.d.ts
2
+ /**
3
+ * Marks a tool as **human-in-the-loop**: the agent pauses and the UI (`render`)
4
+ * supplies the result instead of code. Use it as the tool's `execute`:
5
+ *
6
+ * ```tsx
7
+ * confirm: { execute: hitl(), render: (props) => <Confirm {...props} /> }
8
+ * ```
9
+ *
10
+ * Like {@link defineToolkit}, it has **no runtime implementation**: the
11
+ * `@assistant-ui/next` compiler detects `execute: hitl()`, drops it, and stamps
12
+ * the tool `type: "human"`. Reaching it at runtime means the module wasn't
13
+ * compiled (used outside a `"use generative"` file), so it throws.
14
+ */
15
+ declare function hitl(): never;
16
+ //#endregion
17
+ export { hitl };
18
+ //# sourceMappingURL=hitl.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hitl.d.ts","names":[],"sources":["../src/hitl.ts"],"mappings":";;AAaA;;;;AAAoB;;;;;;;;iBAAJ,IAAA"}
package/dist/hitl.js ADDED
@@ -0,0 +1,21 @@
1
+ //#region src/hitl.ts
2
+ /**
3
+ * Marks a tool as **human-in-the-loop**: the agent pauses and the UI (`render`)
4
+ * supplies the result instead of code. Use it as the tool's `execute`:
5
+ *
6
+ * ```tsx
7
+ * confirm: { execute: hitl(), render: (props) => <Confirm {...props} /> }
8
+ * ```
9
+ *
10
+ * Like {@link defineToolkit}, it has **no runtime implementation**: the
11
+ * `@assistant-ui/next` compiler detects `execute: hitl()`, drops it, and stamps
12
+ * the tool `type: "human"`. Reaching it at runtime means the module wasn't
13
+ * compiled (used outside a `"use generative"` file), so it throws.
14
+ */
15
+ function hitl() {
16
+ throw new Error("[assistant-ui/next] hitl() has no runtime implementation — it marks a human-in-the-loop tool and is stripped at build time by the use-generative compiler. Reaching it means this module was not compiled (e.g. hitl() used outside a \"use generative\" file).");
17
+ }
18
+ //#endregion
19
+ export { hitl };
20
+
21
+ //# sourceMappingURL=hitl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hitl.js","names":[],"sources":["../src/hitl.ts"],"sourcesContent":["/**\n * Marks a tool as **human-in-the-loop**: the agent pauses and the UI (`render`)\n * supplies the result instead of code. Use it as the tool's `execute`:\n *\n * ```tsx\n * confirm: { execute: hitl(), render: (props) => <Confirm {...props} /> }\n * ```\n *\n * Like {@link defineToolkit}, it has **no runtime implementation**: the\n * `@assistant-ui/next` compiler detects `execute: hitl()`, drops it, and stamps\n * the tool `type: \"human\"`. Reaching it at runtime means the module wasn't\n * compiled (used outside a `\"use generative\"` file), so it throws.\n */\nexport function hitl(): never {\n throw new Error(\n \"[assistant-ui/next] hitl() has no runtime implementation — it marks a \" +\n \"human-in-the-loop tool and is stripped at build time by the \" +\n \"use-generative compiler. Reaching it means this module was not compiled \" +\n '(e.g. hitl() used outside a \"use generative\" file).',\n );\n}\n"],"mappings":";;;;;;;;;;;;;;AAaA,SAAgB,OAAc;CAC5B,MAAM,IAAI,MACR,iQAIF;AACF"}
@@ -0,0 +1,4 @@
1
+ import { defineToolkit } from "./define-toolkit.js";
2
+ import { hitl } from "./hitl.js";
3
+ import { WithAuiOptions, withAui } from "./with-aui.js";
4
+ export { type WithAuiOptions, defineToolkit, hitl, withAui };
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ import { defineToolkit } from "./define-toolkit.js";
2
+ import { hitl } from "./hitl.js";
3
+ import { withAui } from "./with-aui.js";
4
+ export { defineToolkit, hitl, withAui };
@@ -0,0 +1,16 @@
1
+ //#region src/loader.d.ts
2
+ /** The subset of the webpack/Turbopack loader context this loader reads. */
3
+ interface GenerativeLoaderContext {
4
+ resourcePath?: string;
5
+ resourceQuery?: string;
6
+ sourceMap?: boolean;
7
+ getOptions?(): {
8
+ path?: string;
9
+ } | undefined;
10
+ async(): (err: unknown, code?: string, map?: object | null) => void;
11
+ }
12
+ /** Webpack/Turbopack loader for `"use generative"` modules. See DESIGN.md. */
13
+ declare function generativeLoader(this: GenerativeLoaderContext, source: string): void;
14
+ //#endregion
15
+ export { generativeLoader as default };
16
+ //# sourceMappingURL=loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.d.ts","names":[],"sources":["../src/loader.ts"],"mappings":";;UAeU,uBAAA;EACR,YAAA;EACA,aAAA;EACA,SAAA;EACA,UAAA;IAAiB,IAAA;EAAA;EACjB,KAAA,KAAU,GAAA,WAAc,IAAA,WAAe,GAAA;AAAA;;iBAuDjB,gBAAA,CACtB,IAAA,EAAM,uBAAuB,EAC7B,MAAA"}
package/dist/loader.js ADDED
@@ -0,0 +1,85 @@
1
+ import * as nodePath from "node:path";
2
+ import { compileGenerative, isGenerativeModule } from "@assistant-ui/x-generative-compiler";
3
+ //#region src/loader.ts
4
+ /** This package's name, used in the facade's re-export specifier. */
5
+ const PKG = "@assistant-ui/next";
6
+ /** Basenames of the react-server-conditioned indirection modules (see with-aui.ts). */
7
+ const SERVER_INDIRECTION = "bundler-redirect.server";
8
+ const CLIENT_INDIRECTION = "bundler-redirect.client";
9
+ /** Whether this resolution is one of the package's indirection modules. */
10
+ function indirectionVariant(resourcePath) {
11
+ const base = nodePath.basename(resourcePath);
12
+ if (base.startsWith(SERVER_INDIRECTION)) return "server";
13
+ if (base.startsWith(CLIENT_INDIRECTION)) return "client";
14
+ return null;
15
+ }
16
+ /** The concrete build forced by a `?generative-env=client|server` resource query. */
17
+ function queryTarget(resourceQuery) {
18
+ if (!resourceQuery) return null;
19
+ const g = new URLSearchParams(resourceQuery).get("generative-env");
20
+ return g === "server" || g === "client" ? g : null;
21
+ }
22
+ /**
23
+ * Facade for a bare generative import: delegates build selection to the
24
+ * `react-server`-conditioned `/bundler-redirect` subpath, passing the module's
25
+ * path via a Turbopack import attribute. See DESIGN.md.
26
+ */
27
+ function buildFacade(resourcePath) {
28
+ return [
29
+ `import toolkit from "${PKG}/bundler-redirect" ${`with { turbopackLoader: "${PKG}/loader", turbopackLoaderOptions: ${JSON.stringify(JSON.stringify({ path: resourcePath }))} }`};`,
30
+ `export default toolkit;`,
31
+ ``
32
+ ].join("\n");
33
+ }
34
+ /**
35
+ * Replaces an indirection module with a re-export of the chosen concrete build,
36
+ * via a relative specifier (Turbopack won't resolve an absolute one). See
37
+ * DESIGN.md.
38
+ */
39
+ function buildIndirection(variant, fromPath, toPath) {
40
+ let rel = nodePath.relative(nodePath.dirname(fromPath), toPath).replace(/\\/g, "/");
41
+ if (!rel.startsWith(".")) rel = `./${rel}`;
42
+ return `export { default } from ${JSON.stringify(`${rel}?generative-env=${variant}`)};\n`;
43
+ }
44
+ /** Webpack/Turbopack loader for `"use generative"` modules. See DESIGN.md. */
45
+ function generativeLoader(source) {
46
+ const callback = this.async();
47
+ const resourcePath = this.resourcePath ?? "";
48
+ const variant = indirectionVariant(resourcePath);
49
+ if (variant) {
50
+ const path = this.getOptions?.()?.path;
51
+ if (!path) {
52
+ callback(/* @__PURE__ */ new Error("[assistant-ui/next] indirection module loaded without a `path` option; it must be imported via the generated facade."));
53
+ return;
54
+ }
55
+ callback(null, buildIndirection(variant, resourcePath, path));
56
+ return;
57
+ }
58
+ const target = queryTarget(this.resourceQuery);
59
+ if (target) {
60
+ if (!isGenerativeModule(source)) {
61
+ callback(null, source);
62
+ return;
63
+ }
64
+ try {
65
+ const { code, map } = compileGenerative(source, {
66
+ target,
67
+ filename: resourcePath,
68
+ sourceMaps: this.sourceMap ?? false
69
+ });
70
+ callback(null, code, map);
71
+ } catch (error) {
72
+ callback(error);
73
+ }
74
+ return;
75
+ }
76
+ if (isGenerativeModule(source)) {
77
+ callback(null, buildFacade(resourcePath));
78
+ return;
79
+ }
80
+ callback(null, source);
81
+ }
82
+ //#endregion
83
+ export { generativeLoader as default };
84
+
85
+ //# sourceMappingURL=loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.js","names":[],"sources":["../src/loader.ts"],"sourcesContent":["import * as nodePath from \"node:path\";\nimport {\n compileGenerative,\n isGenerativeModule,\n type Target,\n} from \"@assistant-ui/x-generative-compiler\";\n\n/** This package's name, used in the facade's re-export specifier. */\nconst PKG = \"@assistant-ui/next\";\n\n/** Basenames of the react-server-conditioned indirection modules (see with-aui.ts). */\nconst SERVER_INDIRECTION = \"bundler-redirect.server\";\nconst CLIENT_INDIRECTION = \"bundler-redirect.client\";\n\n/** The subset of the webpack/Turbopack loader context this loader reads. */\ninterface GenerativeLoaderContext {\n resourcePath?: string;\n resourceQuery?: string;\n sourceMap?: boolean;\n getOptions?(): { path?: string } | undefined;\n async(): (err: unknown, code?: string, map?: object | null) => void;\n}\n\n/** Whether this resolution is one of the package's indirection modules. */\nfunction indirectionVariant(resourcePath: string): Target | null {\n const base = nodePath.basename(resourcePath);\n if (base.startsWith(SERVER_INDIRECTION)) return \"server\";\n if (base.startsWith(CLIENT_INDIRECTION)) return \"client\";\n return null;\n}\n\n/** The concrete build forced by a `?generative-env=client|server` resource query. */\nfunction queryTarget(resourceQuery: string | undefined): Target | null {\n if (!resourceQuery) return null;\n // URLSearchParams strips a leading \"?\" per spec.\n const g = new URLSearchParams(resourceQuery).get(\"generative-env\");\n return g === \"server\" || g === \"client\" ? g : null;\n}\n\n/**\n * Facade for a bare generative import: delegates build selection to the\n * `react-server`-conditioned `/bundler-redirect` subpath, passing the module's\n * path via a Turbopack import attribute. See DESIGN.md.\n */\nfunction buildFacade(resourcePath: string): string {\n const options = JSON.stringify(JSON.stringify({ path: resourcePath }));\n const attr =\n `with { turbopackLoader: \"${PKG}/loader\", ` +\n `turbopackLoaderOptions: ${options} }`;\n return [\n `import toolkit from \"${PKG}/bundler-redirect\" ${attr};`,\n `export default toolkit;`,\n ``,\n ].join(\"\\n\");\n}\n\n/**\n * Replaces an indirection module with a re-export of the chosen concrete build,\n * via a relative specifier (Turbopack won't resolve an absolute one). See\n * DESIGN.md.\n */\nfunction buildIndirection(\n variant: Target,\n fromPath: string,\n toPath: string,\n): string {\n let rel = nodePath\n .relative(nodePath.dirname(fromPath), toPath)\n .replace(/\\\\/g, \"/\");\n if (!rel.startsWith(\".\")) rel = `./${rel}`;\n const spec = JSON.stringify(`${rel}?generative-env=${variant}`);\n return `export { default } from ${spec};\\n`;\n}\n\n/** Webpack/Turbopack loader for `\"use generative\"` modules. See DESIGN.md. */\nexport default function generativeLoader(\n this: GenerativeLoaderContext,\n source: string,\n): void {\n const callback = this.async();\n const resourcePath = this.resourcePath ?? \"\";\n\n // 1) Package indirection (resolved via the `react-server` condition).\n const variant = indirectionVariant(resourcePath);\n if (variant) {\n const path = this.getOptions?.()?.path;\n if (!path) {\n callback(\n new Error(\n \"[assistant-ui/next] indirection module loaded without a `path` \" +\n \"option; it must be imported via the generated facade.\",\n ),\n );\n return;\n }\n callback(null, buildIndirection(variant, resourcePath, path));\n return;\n }\n\n // 2) Explicit concrete-build query (used by the indirection).\n const target = queryTarget(this.resourceQuery);\n if (target) {\n if (!isGenerativeModule(source)) {\n callback(null, source);\n return;\n }\n try {\n const { code, map } = compileGenerative(source, {\n target,\n filename: resourcePath,\n sourceMaps: this.sourceMap ?? false,\n });\n callback(null, code, map);\n } catch (error) {\n callback(error);\n }\n return;\n }\n\n // 3) Bare import of a generative module → facade.\n if (isGenerativeModule(source)) {\n callback(null, buildFacade(resourcePath));\n return;\n }\n\n // 4) Not a generative module.\n callback(null, source);\n}\n"],"mappings":";;;;AAQA,MAAM,MAAM;;AAGZ,MAAM,qBAAqB;AAC3B,MAAM,qBAAqB;;AAY3B,SAAS,mBAAmB,cAAqC;CAC/D,MAAM,OAAO,SAAS,SAAS,YAAY;CAC3C,IAAI,KAAK,WAAW,kBAAkB,GAAG,OAAO;CAChD,IAAI,KAAK,WAAW,kBAAkB,GAAG,OAAO;CAChD,OAAO;AACT;;AAGA,SAAS,YAAY,eAAkD;CACrE,IAAI,CAAC,eAAe,OAAO;CAE3B,MAAM,IAAI,IAAI,gBAAgB,aAAa,EAAE,IAAI,gBAAgB;CACjE,OAAO,MAAM,YAAY,MAAM,WAAW,IAAI;AAChD;;;;;;AAOA,SAAS,YAAY,cAA8B;CAKjD,OAAO;EACL,wBAAwB,IAAI,qBAAqB,4BAHrB,IAAI,oCAFlB,KAAK,UAAU,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC,CAGjC,EAAE,IAEmB;EACtD;EACA;CACF,EAAE,KAAK,IAAI;AACb;;;;;;AAOA,SAAS,iBACP,SACA,UACA,QACQ;CACR,IAAI,MAAM,SACP,SAAS,SAAS,QAAQ,QAAQ,GAAG,MAAM,EAC3C,QAAQ,OAAO,GAAG;CACrB,IAAI,CAAC,IAAI,WAAW,GAAG,GAAG,MAAM,KAAK;CAErC,OAAO,2BADM,KAAK,UAAU,GAAG,IAAI,kBAAkB,SAChB,EAAE;AACzC;;AAGA,SAAwB,iBAEtB,QACM;CACN,MAAM,WAAW,KAAK,MAAM;CAC5B,MAAM,eAAe,KAAK,gBAAgB;CAG1C,MAAM,UAAU,mBAAmB,YAAY;CAC/C,IAAI,SAAS;EACX,MAAM,OAAO,KAAK,aAAa,GAAG;EAClC,IAAI,CAAC,MAAM;GACT,yBACE,IAAI,MACF,sHAEF,CACF;GACA;EACF;EACA,SAAS,MAAM,iBAAiB,SAAS,cAAc,IAAI,CAAC;EAC5D;CACF;CAGA,MAAM,SAAS,YAAY,KAAK,aAAa;CAC7C,IAAI,QAAQ;EACV,IAAI,CAAC,mBAAmB,MAAM,GAAG;GAC/B,SAAS,MAAM,MAAM;GACrB;EACF;EACA,IAAI;GACF,MAAM,EAAE,MAAM,QAAQ,kBAAkB,QAAQ;IAC9C;IACA,UAAU;IACV,YAAY,KAAK,aAAa;GAChC,CAAC;GACD,SAAS,MAAM,MAAM,GAAG;EAC1B,SAAS,OAAO;GACd,SAAS,KAAK;EAChB;EACA;CACF;CAGA,IAAI,mBAAmB,MAAM,GAAG;EAC9B,SAAS,MAAM,YAAY,YAAY,CAAC;EACxC;CACF;CAGA,SAAS,MAAM,MAAM;AACvB"}
@@ -0,0 +1,29 @@
1
+ //#region src/with-aui.d.ts
2
+ interface WithAuiOptions {
3
+ /**
4
+ * Globs scanned for the `"use generative"` directive (default: all TS/TSX).
5
+ * Narrow it (e.g. `["*.generative.tsx"]`) to limit what passes through the loader.
6
+ */
7
+ rules?: string[];
8
+ }
9
+ type NextConfigLike = {
10
+ turbopack?: {
11
+ rules?: Record<string, unknown>;
12
+ } | undefined;
13
+ webpack?: ((config: any, context: any) => any) | null | undefined;
14
+ };
15
+ /**
16
+ * Wraps a Next.js config so `"use generative"` modules are compiled per build
17
+ * target. Detection is by directive, not filename. See DESIGN.md.
18
+ *
19
+ * @example
20
+ * ```ts
21
+ * // next.config.ts
22
+ * import { withAui } from "@assistant-ui/next";
23
+ * export default withAui({ ...yourConfig });
24
+ * ```
25
+ */
26
+ declare function withAui<T extends NextConfigLike>(nextConfig?: T, options?: WithAuiOptions): T;
27
+ //#endregion
28
+ export { WithAuiOptions, withAui };
29
+ //# sourceMappingURL=with-aui.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"with-aui.d.ts","names":[],"sources":["../src/with-aui.ts"],"mappings":";UAEiB,cAAA;EAAA;;;;EAKf,KAAK;AAAA;AAAA,KAIF,cAAA;EACH,SAAA;IAAc,KAAA,GAAQ,MAAM;EAAA;EAC5B,OAAA,KAAY,MAAA,OAAa,OAAA;AAAA;;;;;AAAY;AAcvC;;;;;;iBAAgB,OAAA,WAAkB,cAAA,EAChC,UAAA,GAAY,CAAA,EACZ,OAAA,GAAS,cAAA,GACR,CAAA"}
@@ -0,0 +1,45 @@
1
+ //#region src/with-aui.ts
2
+ const LOADER = "@assistant-ui/next/loader";
3
+ /**
4
+ * Wraps a Next.js config so `"use generative"` modules are compiled per build
5
+ * target. Detection is by directive, not filename. See DESIGN.md.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * // next.config.ts
10
+ * import { withAui } from "@assistant-ui/next";
11
+ * export default withAui({ ...yourConfig });
12
+ * ```
13
+ */
14
+ function withAui(nextConfig = {}, options = {}) {
15
+ const globs = options.rules ?? ["*.ts", "*.tsx"];
16
+ const rules = { ...nextConfig.turbopack?.rules };
17
+ for (const glob of globs) {
18
+ const existing = rules[glob];
19
+ const existingLoaders = existing && typeof existing === "object" && Array.isArray(existing.loaders) ? existing.loaders : [];
20
+ rules[glob] = {
21
+ ...existing,
22
+ loaders: [...existingLoaders, LOADER]
23
+ };
24
+ }
25
+ const userWebpack = nextConfig.webpack;
26
+ return {
27
+ ...nextConfig,
28
+ turbopack: {
29
+ ...nextConfig.turbopack,
30
+ rules
31
+ },
32
+ webpack(config, context) {
33
+ config.module.rules.push({
34
+ test: /\.[jt]sx?$/,
35
+ exclude: /node_modules/,
36
+ use: [LOADER]
37
+ });
38
+ return userWebpack ? userWebpack(config, context) : config;
39
+ }
40
+ };
41
+ }
42
+ //#endregion
43
+ export { withAui };
44
+
45
+ //# sourceMappingURL=with-aui.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"with-aui.js","names":[],"sources":["../src/with-aui.ts"],"sourcesContent":["const LOADER = \"@assistant-ui/next/loader\";\n\nexport interface WithAuiOptions {\n /**\n * Globs scanned for the `\"use generative\"` directive (default: all TS/TSX).\n * Narrow it (e.g. `[\"*.generative.tsx\"]`) to limit what passes through the loader.\n */\n rules?: string[];\n}\n\n// Loosely typed so this module doesn't need `next` as a dependency.\ntype NextConfigLike = {\n turbopack?: { rules?: Record<string, unknown> } | undefined;\n webpack?: ((config: any, context: any) => any) | null | undefined;\n};\n\n/**\n * Wraps a Next.js config so `\"use generative\"` modules are compiled per build\n * target. Detection is by directive, not filename. See DESIGN.md.\n *\n * @example\n * ```ts\n * // next.config.ts\n * import { withAui } from \"@assistant-ui/next\";\n * export default withAui({ ...yourConfig });\n * ```\n */\nexport function withAui<T extends NextConfigLike>(\n nextConfig: T = {} as T,\n options: WithAuiOptions = {},\n): T {\n const globs = options.rules ?? [\"*.ts\", \"*.tsx\"];\n // Merge into the user's rules: if a glob already has a `{ loaders }` rule,\n // append ours rather than clobbering it (see DESIGN.md).\n const rules: Record<string, unknown> = { ...nextConfig.turbopack?.rules };\n for (const glob of globs) {\n const existing = rules[glob];\n const existingLoaders =\n existing &&\n typeof existing === \"object\" &&\n Array.isArray((existing as { loaders?: unknown }).loaders)\n ? (existing as { loaders: unknown[] }).loaders\n : [];\n rules[glob] = {\n ...(existing as object),\n loaders: [...existingLoaders, LOADER],\n };\n }\n\n const userWebpack = nextConfig.webpack;\n\n return {\n ...nextConfig,\n turbopack: {\n ...nextConfig.turbopack,\n rules,\n },\n webpack(config: any, context: any) {\n // Turbopack-only facade; under webpack, import concrete builds explicitly\n // with `?generative-env=server` / `=client`.\n config.module.rules.push({\n test: /\\.[jt]sx?$/,\n exclude: /node_modules/,\n use: [LOADER],\n });\n return userWebpack ? userWebpack(config, context) : config;\n },\n } as T;\n}\n"],"mappings":";AAAA,MAAM,SAAS;;;;;;;;;;;;AA2Bf,SAAgB,QACd,aAAgB,CAAC,GACjB,UAA0B,CAAC,GACxB;CACH,MAAM,QAAQ,QAAQ,SAAS,CAAC,QAAQ,OAAO;CAG/C,MAAM,QAAiC,EAAE,GAAG,WAAW,WAAW,MAAM;CACxE,KAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,WAAW,MAAM;EACvB,MAAM,kBACJ,YACA,OAAO,aAAa,YACpB,MAAM,QAAS,SAAmC,OAAO,IACpD,SAAoC,UACrC,CAAC;EACP,MAAM,QAAQ;GACZ,GAAI;GACJ,SAAS,CAAC,GAAG,iBAAiB,MAAM;EACtC;CACF;CAEA,MAAM,cAAc,WAAW;CAE/B,OAAO;EACL,GAAG;EACH,WAAW;GACT,GAAG,WAAW;GACd;EACF;EACA,QAAQ,QAAa,SAAc;GAGjC,OAAO,OAAO,MAAM,KAAK;IACvB,MAAM;IACN,SAAS;IACT,KAAK,CAAC,MAAM;GACd,CAAC;GACD,OAAO,cAAc,YAAY,QAAQ,OAAO,IAAI;EACtD;CACF;AACF"}
package/package.json ADDED
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "@assistant-ui/next",
3
+ "version": "0.0.1",
4
+ "description": "Next.js integration for assistant-ui: the withAui() config wrapper and the \"use generative\" directive compiler that colocates a tool's schema, server-only execute, and client-only render in one file.",
5
+ "keywords": [
6
+ "assistant-ui",
7
+ "next",
8
+ "nextjs",
9
+ "generative",
10
+ "rsc",
11
+ "use client",
12
+ "server-only",
13
+ "compiler",
14
+ "loader"
15
+ ],
16
+ "author": "AgentbaseAI Inc.",
17
+ "license": "MIT",
18
+ "type": "module",
19
+ "exports": {
20
+ ".": {
21
+ "types": "./dist/index.d.ts",
22
+ "default": "./dist/index.js"
23
+ },
24
+ "./loader": {
25
+ "types": "./dist/loader.d.ts",
26
+ "default": "./dist/loader.js"
27
+ },
28
+ "./bundler-redirect": {
29
+ "react-server": "./dist/bundler-redirect.server.js",
30
+ "default": "./dist/bundler-redirect.client.js"
31
+ }
32
+ },
33
+ "main": "./dist/index.js",
34
+ "types": "./dist/index.d.ts",
35
+ "files": [
36
+ "dist",
37
+ "src",
38
+ "SPEC.md",
39
+ "README.md"
40
+ ],
41
+ "sideEffects": false,
42
+ "scripts": {
43
+ "build": "aui-build",
44
+ "test": "vitest run --passWithNoTests",
45
+ "test:watch": "vitest"
46
+ },
47
+ "dependencies": {
48
+ "@assistant-ui/x-generative-compiler": "workspace:^"
49
+ },
50
+ "devDependencies": {
51
+ "@assistant-ui/x-buildutils": "workspace:*",
52
+ "@types/node": "^25.9.1",
53
+ "vitest": "^4.1.7"
54
+ },
55
+ "publishConfig": {
56
+ "access": "public",
57
+ "provenance": true
58
+ },
59
+ "homepage": "https://www.assistant-ui.com/",
60
+ "repository": {
61
+ "type": "git",
62
+ "url": "git+https://github.com/assistant-ui/assistant-ui.git",
63
+ "directory": "packages/next"
64
+ },
65
+ "bugs": {
66
+ "url": "https://github.com/assistant-ui/assistant-ui/issues"
67
+ }
68
+ }
@@ -0,0 +1,7 @@
1
+ // Internal default-condition indirection target (SSR + browser) — always
2
+ // replaced by the @assistant-ui/next loader, which re-exports the module's
3
+ // client build. Reaching this means the loader wasn't applied (see DESIGN.md).
4
+ throw new Error(
5
+ "@assistant-ui/next/bundler-redirect is internal; import it through the " +
6
+ "@assistant-ui/next loader.",
7
+ );
@@ -0,0 +1,7 @@
1
+ // Internal `react-server` indirection target — always replaced by the
2
+ // @assistant-ui/next loader, which re-exports the module's server build.
3
+ // Reaching this means the loader wasn't applied (see DESIGN.md).
4
+ throw new Error(
5
+ "@assistant-ui/next/bundler-redirect is internal; import it through the " +
6
+ "@assistant-ui/next loader.",
7
+ );
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export { withAui, type WithAuiOptions } from "./with-aui";
2
+ export { defineToolkit } from "./define-toolkit";
3
+ export { hitl } from "./hitl";
package/src/loader.ts ADDED
@@ -0,0 +1,128 @@
1
+ import * as nodePath from "node:path";
2
+ import {
3
+ compileGenerative,
4
+ isGenerativeModule,
5
+ type Target,
6
+ } from "@assistant-ui/x-generative-compiler";
7
+
8
+ /** This package's name, used in the facade's re-export specifier. */
9
+ const PKG = "@assistant-ui/next";
10
+
11
+ /** Basenames of the react-server-conditioned indirection modules (see with-aui.ts). */
12
+ const SERVER_INDIRECTION = "bundler-redirect.server";
13
+ const CLIENT_INDIRECTION = "bundler-redirect.client";
14
+
15
+ /** The subset of the webpack/Turbopack loader context this loader reads. */
16
+ interface GenerativeLoaderContext {
17
+ resourcePath?: string;
18
+ resourceQuery?: string;
19
+ sourceMap?: boolean;
20
+ getOptions?(): { path?: string } | undefined;
21
+ async(): (err: unknown, code?: string, map?: object | null) => void;
22
+ }
23
+
24
+ /** Whether this resolution is one of the package's indirection modules. */
25
+ function indirectionVariant(resourcePath: string): Target | null {
26
+ const base = nodePath.basename(resourcePath);
27
+ if (base.startsWith(SERVER_INDIRECTION)) return "server";
28
+ if (base.startsWith(CLIENT_INDIRECTION)) return "client";
29
+ return null;
30
+ }
31
+
32
+ /** The concrete build forced by a `?generative-env=client|server` resource query. */
33
+ function queryTarget(resourceQuery: string | undefined): Target | null {
34
+ if (!resourceQuery) return null;
35
+ // URLSearchParams strips a leading "?" per spec.
36
+ const g = new URLSearchParams(resourceQuery).get("generative-env");
37
+ return g === "server" || g === "client" ? g : null;
38
+ }
39
+
40
+ /**
41
+ * Facade for a bare generative import: delegates build selection to the
42
+ * `react-server`-conditioned `/bundler-redirect` subpath, passing the module's
43
+ * path via a Turbopack import attribute. See DESIGN.md.
44
+ */
45
+ function buildFacade(resourcePath: string): string {
46
+ const options = JSON.stringify(JSON.stringify({ path: resourcePath }));
47
+ const attr =
48
+ `with { turbopackLoader: "${PKG}/loader", ` +
49
+ `turbopackLoaderOptions: ${options} }`;
50
+ return [
51
+ `import toolkit from "${PKG}/bundler-redirect" ${attr};`,
52
+ `export default toolkit;`,
53
+ ``,
54
+ ].join("\n");
55
+ }
56
+
57
+ /**
58
+ * Replaces an indirection module with a re-export of the chosen concrete build,
59
+ * via a relative specifier (Turbopack won't resolve an absolute one). See
60
+ * DESIGN.md.
61
+ */
62
+ function buildIndirection(
63
+ variant: Target,
64
+ fromPath: string,
65
+ toPath: string,
66
+ ): string {
67
+ let rel = nodePath
68
+ .relative(nodePath.dirname(fromPath), toPath)
69
+ .replace(/\\/g, "/");
70
+ if (!rel.startsWith(".")) rel = `./${rel}`;
71
+ const spec = JSON.stringify(`${rel}?generative-env=${variant}`);
72
+ return `export { default } from ${spec};\n`;
73
+ }
74
+
75
+ /** Webpack/Turbopack loader for `"use generative"` modules. See DESIGN.md. */
76
+ export default function generativeLoader(
77
+ this: GenerativeLoaderContext,
78
+ source: string,
79
+ ): void {
80
+ const callback = this.async();
81
+ const resourcePath = this.resourcePath ?? "";
82
+
83
+ // 1) Package indirection (resolved via the `react-server` condition).
84
+ const variant = indirectionVariant(resourcePath);
85
+ if (variant) {
86
+ const path = this.getOptions?.()?.path;
87
+ if (!path) {
88
+ callback(
89
+ new Error(
90
+ "[assistant-ui/next] indirection module loaded without a `path` " +
91
+ "option; it must be imported via the generated facade.",
92
+ ),
93
+ );
94
+ return;
95
+ }
96
+ callback(null, buildIndirection(variant, resourcePath, path));
97
+ return;
98
+ }
99
+
100
+ // 2) Explicit concrete-build query (used by the indirection).
101
+ const target = queryTarget(this.resourceQuery);
102
+ if (target) {
103
+ if (!isGenerativeModule(source)) {
104
+ callback(null, source);
105
+ return;
106
+ }
107
+ try {
108
+ const { code, map } = compileGenerative(source, {
109
+ target,
110
+ filename: resourcePath,
111
+ sourceMaps: this.sourceMap ?? false,
112
+ });
113
+ callback(null, code, map);
114
+ } catch (error) {
115
+ callback(error);
116
+ }
117
+ return;
118
+ }
119
+
120
+ // 3) Bare import of a generative module → facade.
121
+ if (isGenerativeModule(source)) {
122
+ callback(null, buildFacade(resourcePath));
123
+ return;
124
+ }
125
+
126
+ // 4) Not a generative module.
127
+ callback(null, source);
128
+ }
@@ -0,0 +1,69 @@
1
+ const LOADER = "@assistant-ui/next/loader";
2
+
3
+ export interface WithAuiOptions {
4
+ /**
5
+ * Globs scanned for the `"use generative"` directive (default: all TS/TSX).
6
+ * Narrow it (e.g. `["*.generative.tsx"]`) to limit what passes through the loader.
7
+ */
8
+ rules?: string[];
9
+ }
10
+
11
+ // Loosely typed so this module doesn't need `next` as a dependency.
12
+ type NextConfigLike = {
13
+ turbopack?: { rules?: Record<string, unknown> } | undefined;
14
+ webpack?: ((config: any, context: any) => any) | null | undefined;
15
+ };
16
+
17
+ /**
18
+ * Wraps a Next.js config so `"use generative"` modules are compiled per build
19
+ * target. Detection is by directive, not filename. See DESIGN.md.
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * // next.config.ts
24
+ * import { withAui } from "@assistant-ui/next";
25
+ * export default withAui({ ...yourConfig });
26
+ * ```
27
+ */
28
+ export function withAui<T extends NextConfigLike>(
29
+ nextConfig: T = {} as T,
30
+ options: WithAuiOptions = {},
31
+ ): T {
32
+ const globs = options.rules ?? ["*.ts", "*.tsx"];
33
+ // Merge into the user's rules: if a glob already has a `{ loaders }` rule,
34
+ // append ours rather than clobbering it (see DESIGN.md).
35
+ const rules: Record<string, unknown> = { ...nextConfig.turbopack?.rules };
36
+ for (const glob of globs) {
37
+ const existing = rules[glob];
38
+ const existingLoaders =
39
+ existing &&
40
+ typeof existing === "object" &&
41
+ Array.isArray((existing as { loaders?: unknown }).loaders)
42
+ ? (existing as { loaders: unknown[] }).loaders
43
+ : [];
44
+ rules[glob] = {
45
+ ...(existing as object),
46
+ loaders: [...existingLoaders, LOADER],
47
+ };
48
+ }
49
+
50
+ const userWebpack = nextConfig.webpack;
51
+
52
+ return {
53
+ ...nextConfig,
54
+ turbopack: {
55
+ ...nextConfig.turbopack,
56
+ rules,
57
+ },
58
+ webpack(config: any, context: any) {
59
+ // Turbopack-only facade; under webpack, import concrete builds explicitly
60
+ // with `?generative-env=server` / `=client`.
61
+ config.module.rules.push({
62
+ test: /\.[jt]sx?$/,
63
+ exclude: /node_modules/,
64
+ use: [LOADER],
65
+ });
66
+ return userWebpack ? userWebpack(config, context) : config;
67
+ },
68
+ } as T;
69
+ }