@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 +100 -0
- package/SPEC.md +154 -0
- package/dist/bundler-redirect.client.d.ts +1 -0
- package/dist/bundler-redirect.client.js +5 -0
- package/dist/bundler-redirect.client.js.map +1 -0
- package/dist/bundler-redirect.server.d.ts +1 -0
- package/dist/bundler-redirect.server.js +5 -0
- package/dist/bundler-redirect.server.js.map +1 -0
- package/dist/define-toolkit.d.ts +19 -0
- package/dist/define-toolkit.d.ts.map +1 -0
- package/dist/define-toolkit.js +20 -0
- package/dist/define-toolkit.js.map +1 -0
- package/dist/hitl.d.ts +18 -0
- package/dist/hitl.d.ts.map +1 -0
- package/dist/hitl.js +21 -0
- package/dist/hitl.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/loader.d.ts +16 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/loader.js +85 -0
- package/dist/loader.js.map +1 -0
- package/dist/with-aui.d.ts +29 -0
- package/dist/with-aui.d.ts.map +1 -0
- package/dist/with-aui.js +45 -0
- package/dist/with-aui.js.map +1 -0
- package/package.json +68 -0
- package/src/bundler-redirect.client.ts +7 -0
- package/src/bundler-redirect.server.ts +7 -0
- package/src/index.ts +3 -0
- package/src/loader.ts +128 -0
- package/src/with-aui.ts +69 -0
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 @@
|
|
|
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 @@
|
|
|
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
|
package/dist/hitl.js.map
ADDED
|
@@ -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"}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
package/dist/loader.d.ts
ADDED
|
@@ -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"}
|
package/dist/with-aui.js
ADDED
|
@@ -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
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
|
+
}
|
package/src/with-aui.ts
ADDED
|
@@ -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
|
+
}
|