@nuwax-ai/openui-mcp 0.1.11 → 0.2.0

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.
Files changed (56) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +66 -289
  3. package/dist/server/artifact-store.d.ts +14 -6
  4. package/dist/server/artifact-store.d.ts.map +1 -1
  5. package/dist/server/artifact-store.js +73 -9
  6. package/dist/server/artifact-store.js.map +1 -1
  7. package/dist/server/contracts.d.ts +68 -0
  8. package/dist/server/contracts.d.ts.map +1 -1
  9. package/dist/server/contracts.js +46 -1
  10. package/dist/server/contracts.js.map +1 -1
  11. package/dist/server/index.d.ts +1 -0
  12. package/dist/server/index.d.ts.map +1 -1
  13. package/dist/server/index.js +1 -0
  14. package/dist/server/index.js.map +1 -1
  15. package/dist/server/mcp-server.d.ts.map +1 -1
  16. package/dist/server/mcp-server.js +4 -6
  17. package/dist/server/mcp-server.js.map +1 -1
  18. package/dist/server/project-root.d.ts +11 -0
  19. package/dist/server/project-root.d.ts.map +1 -0
  20. package/dist/server/project-root.js +51 -0
  21. package/dist/server/project-root.js.map +1 -0
  22. package/dist/server/render-service.d.ts +3 -8
  23. package/dist/server/render-service.d.ts.map +1 -1
  24. package/dist/server/render-service.js +21 -26
  25. package/dist/server/render-service.js.map +1 -1
  26. package/dist/server/runtime.d.ts +3 -4
  27. package/dist/server/runtime.d.ts.map +1 -1
  28. package/dist/server/runtime.js +4 -7
  29. package/dist/server/runtime.js.map +1 -1
  30. package/dist/server/stdio.js +8 -9
  31. package/dist/server/stdio.js.map +1 -1
  32. package/dist/server/version.d.ts +1 -1
  33. package/dist/server/version.js +1 -1
  34. package/dist/web/{sidecar.css → runtime.css} +1 -1
  35. package/dist/web/{sidecar.js → runtime.js} +7578 -7549
  36. package/package.json +6 -9
  37. package/dist/server/config.d.ts +0 -14
  38. package/dist/server/config.d.ts.map +0 -1
  39. package/dist/server/config.js +0 -50
  40. package/dist/server/config.js.map +0 -1
  41. package/dist/server/http-app.d.ts +0 -11
  42. package/dist/server/http-app.d.ts.map +0 -1
  43. package/dist/server/http-app.js +0 -88
  44. package/dist/server/http-app.js.map +0 -1
  45. package/dist/server/http-server.d.ts +0 -10
  46. package/dist/server/http-server.d.ts.map +0 -1
  47. package/dist/server/http-server.js +0 -30
  48. package/dist/server/http-server.js.map +0 -1
  49. package/dist/server/http.d.ts +0 -3
  50. package/dist/server/http.d.ts.map +0 -1
  51. package/dist/server/http.js +0 -18
  52. package/dist/server/http.js.map +0 -1
  53. package/dist/server/page-template.d.ts +0 -2
  54. package/dist/server/page-template.d.ts.map +0 -1
  55. package/dist/server/page-template.js +0 -32
  56. package/dist/server/page-template.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.0
4
+
5
+ - Persist OpenUI artifacts as `data/{artifactId}.openui.json`.
6
+ - Support atomic updates by reusing `artifactId`.
7
+ - Return lightweight `nuwax.openui-ref/v1` results.
8
+ - Replace the localhost page server with a frozen postMessage Runtime bundle.
9
+ - Remove Artifact TTL, sidecar page URLs, and HTTP server configuration.
10
+
11
+ ## 0.1.12
12
+
13
+ - Treat sidecar `structuredContent.page.url` as an internal local Runtime
14
+ locator and explicitly prohibit Agents from showing it to users.
15
+ - Return only the Artifact ID in the sidecar tool text result. The Nuwax Host
16
+ derives the authenticated user-facing proxy URL from conversation context.
17
+
3
18
  ## 0.1.11
4
19
 
5
20
  - Fix dashboard authoring guidance for reactive filters: empty input/select
package/README.md CHANGED
@@ -1,179 +1,65 @@
1
1
  # Nuwax OpenUI MCP
2
2
 
3
- OpenUI over MCP for Nuwax Agent sessions. One MCP tool publishes a validated,
4
- versioned OpenUI artifact that a host can present in either of two places:
5
-
6
- - **inline** — rendered directly by the host Renderer by default, with an
7
- optional OpenUI Runtime iframe mode;
8
- - **sidecar** — the same Runtime page opened in the preview area or a full page.
9
-
10
- The project intentionally does not replace the host's chat transport, session
11
- model, permission system, or tool status UI.
12
-
13
- ## Status
14
-
15
- This repository contains the first executable foundation:
16
-
17
- - `nuwax_render_openui` MCP tool;
18
- - `nuwax.openui/v1` input and structured-result contract;
19
- - stdio and Streamable HTTP transports;
20
- - direct Renderer and page Runtime options for inline artifacts;
21
- - one OpenUI React page Runtime shared by iframe-inline and sidecar artifacts;
22
- - relative-path and desktop-query transports for proxied Runtime pages;
23
- - `OPENUI_READY` / `OPENUI_RESIZE` page messages for iframe auto-sizing;
24
- - localized required validation, including date and date-range values;
25
- - isolated Runtime styles, independent from the Nuwax host reset stylesheet;
26
- - source policy, artifact TTL, Host validation, CSP, and tests.
27
-
28
- The default artifact store is in-memory. Production deployments should provide
29
- a durable, tenant-aware `ArtifactStore` and authentication at the edge.
3
+ `@nuwax-ai/openui-mcp` provides OpenUI authoring guidance, schema resources,
4
+ validation, and durable artifact generation for Nuwax Agent sessions.
30
5
 
31
6
  ## Architecture
32
7
 
33
- ```text
34
- Agent
35
- └─ MCP call: nuwax_render_openui
36
- └─ validate + persist artifact
37
- ├─ structuredContent → host discovers the artifact
38
- ├─ inline → host Renderer (default)
39
- └─ shared Runtime page
40
- ├─ inline iframe (optional)
41
- └─ sidecar preview / full page
42
- ```
43
-
44
- `presentation.mode` controls where the host presents the Artifact; it does not
45
- force an inline rendering technology. PC hosts should default inline Artifacts
46
- to the direct Renderer and may explicitly select the iframe Runtime for stronger
47
- style isolation or cross-client page reuse. `GET /openui/pages/:id` is available
48
- for both inline and sidecar artifacts. For backward compatibility, only sidecar
49
- structured results currently contain `artifact.page.url`; an iframe-inline host
50
- builds its trusted page URL from its configured proxy route and `artifactId`.
51
-
52
- OpenUI runtime queries and mutations are intentionally not proxied in the first
53
- release. They must be added behind a host-controlled MCP proxy with tenant,
54
- conversation, artifact, allowlist, permission, and audit checks.
55
-
56
- ## Requirements
57
-
58
- - Node.js 20+
59
- - pnpm 10+
60
-
61
- ## Install and verify
62
-
63
- ```bash
64
- pnpm install
65
- pnpm verify
66
- ```
67
-
68
- Install the published package in a host project:
69
-
70
- ```bash
71
- npm install @nuwax-ai/openui-mcp
72
- ```
73
-
74
- ## Run over stdio
8
+ The MCP server does not host pages. It writes OpenUI data into the active
9
+ project and returns a lightweight reference:
75
10
 
76
- ```bash
77
- pnpm build
78
- pnpm dev:stdio
11
+ ```text
12
+ nuwax_render_openui
13
+ -> validate OpenUI Lang
14
+ -> data/{artifactId}.openui.json
15
+ -> nuwax.openui-ref/v1
79
16
  ```
80
17
 
81
- Example MCP client configuration:
18
+ Nuwax PC Web loads the data file and renders it with the frozen static runtime
19
+ at `/openui-runtime/index.html`. Inline, sidecar, and file preview all use that
20
+ same runtime.
82
21
 
83
- ```json
84
- {
85
- "mcpServers": {
86
- "nuwax-openui": {
87
- "command": "node",
88
- "args": ["/absolute/path/to/nuwax-openui-mcp/dist/server/stdio.js"],
89
- "env": {
90
- "NUWAX_OPENUI_BASE_URL": "http://127.0.0.1:8787"
91
- }
92
- }
93
- }
94
- }
95
- ```
96
-
97
- The stdio process starts the sidecar HTTP server by default and both transports
98
- share the same in-memory artifact store. Set
99
- `NUWAX_OPENUI_SIDECAR_SERVER_ENABLED=false` only when another process owns the
100
- page Runtime service. Page-based inline rendering also needs this service.
22
+ ## Installation
101
23
 
102
- For NuwaClaw, configure this server as persistent so all Agent sessions share
103
- one process and one sidecar port:
24
+ Configure the server as a session-scoped stdio MCP so it inherits the current
25
+ project directory:
104
26
 
105
27
  ```json
106
28
  {
107
29
  "mcpServers": {
108
30
  "nuwax-openui": {
109
31
  "command": "npx",
110
- "args": ["-y", "@nuwax-ai/openui-mcp@0.1.11"],
111
- "env": {
112
- "NUWAX_OPENUI_HOST": "127.0.0.1",
113
- "NUWAX_OPENUI_PORT": "8787",
114
- "NUWAX_OPENUI_BASE_URL": "http://127.0.0.1:8787",
115
- "NUWAX_OPENUI_ALLOWED_HOSTS": "127.0.0.1,localhost"
116
- },
117
- "enabled": true,
118
- "persistent": true
32
+ "args": ["-y", "@nuwax-ai/openui-mcp@0.2.0"]
119
33
  }
120
34
  }
121
35
  }
122
36
  ```
123
37
 
124
- > **Note:** The package exposes `openui-mcp` as the default bin (matching the
125
- > unscoped package name) so `npx @nuwax-ai/openui-mcp` resolves to the stdio
126
- > entry. Aliases: `nuwax-openui-mcp` (stdio), `nuwax-openui-mcp-http` (HTTP).
127
-
128
- ## Run over Streamable HTTP
129
-
130
- ```bash
131
- cp .env.example .env
132
- pnpm build
133
- pnpm start
134
- ```
135
-
136
- Endpoints:
137
-
138
- | Endpoint | Purpose |
139
- | --------------------------- | ---------------------------------- |
140
- | `POST /mcp` | MCP Streamable HTTP |
141
- | `GET /healthz` | Health check |
142
- | `GET /openui/artifacts/:id` | Artifact document for the renderer |
143
- | `GET /openui/pages/:id` | Shared inline/sidecar Runtime page |
144
- | `GET /openui/assets/*` | Runtime JavaScript and CSS |
145
-
146
- ### Proxied page transports
147
-
148
- The default page uses relative path assets and Artifact lookup:
38
+ Do not configure it as a persistent/global MCP. If the host cannot provide the
39
+ project as the process working directory or an MCP Root, set
40
+ `NUWAX_OPENUI_PROJECT_ROOT` explicitly.
149
41
 
150
- ```text
151
- GET /openui/pages/{artifactId}
152
- ├─ ../assets/sidecar.css
153
- ├─ ../assets/sidecar.js
154
- └─ ../artifacts/{artifactId}
155
- ```
42
+ The file repository is initialized lazily on the first render call. This avoids
43
+ creating a `data/` directory when a client starts the server only to discover
44
+ its tool list.
156
45
 
157
- For the Nuwax PC desktop/file proxy, request the page with
158
- `?transport=desktop-query`. The generated page keeps all subsequent requests on
159
- the same conversation-scoped URL and changes only the query:
46
+ ## Tools and resources
160
47
 
161
- ```text
162
- ?openui=css
163
- ?openui=js
164
- ?openui=artifact&artifactId={artifactId}
165
- ```
48
+ - `nuwax_render_openui`: creates or updates an Artifact file.
49
+ - `nuwax_get_openui_reference`: returns the authoring guide or schema.
50
+ - `nuwax://openui/schema/v0.5`: renderer-generated component schema.
51
+ - `nuwax://openui/authoring-guide/v0.5`: syntax and examples.
52
+ - `nuwax_openui_authoring`: reusable authoring prompt.
166
53
 
167
- The query transport is a page transport convention, not a general-purpose
168
- proxy. The Nuwax host must derive the conversation and target service from its
169
- authenticated context and must not accept an arbitrary upstream URL.
54
+ Call `nuwax_get_openui_reference` before producing complex forms, charts, or
55
+ dashboards.
170
56
 
171
- ## Tool contract
57
+ ## Creating an Artifact
172
58
 
173
59
  ```json
174
60
  {
175
61
  "schemaVersion": "nuwax.openui/v1",
176
- "title": "Release status",
62
+ "title": "Deployment status",
177
63
  "presentation": {
178
64
  "mode": "inline",
179
65
  "autoOpen": false
@@ -181,164 +67,55 @@ authenticated context and must not accept an arbitrary upstream URL.
181
67
  "document": {
182
68
  "language": "openui-lang",
183
69
  "specVersion": "0.5",
184
- "source": "root = Stack([title])\ntitle = TextContent(\"Release is ready\", \"large-heavy\")"
185
- },
186
- "bindings": {
187
- "tools": []
70
+ "source": "root = Stack([title])\ntitle = TextContent(\"Ready\", \"large-heavy\")"
188
71
  },
189
- "fallback": {
190
- "markdown": "Release is ready."
191
- }
72
+ "bindings": { "tools": [] },
73
+ "fallback": { "markdown": "Deployment is ready." }
192
74
  }
193
75
  ```
194
76
 
195
- The MCP response always contains a text fallback plus machine-readable
196
- `structuredContent`. Hosts must identify OpenUI results by all three fields:
77
+ The response is a `nuwax.openui-ref/v1` pointing to:
197
78
 
198
79
  ```text
199
- tool name = nuwax_render_openui
200
- type = nuwax.openui
201
- schemaVersion = nuwax.openui/v1
202
- ```
203
-
204
- Do not infer UI from arbitrary MCP tool results.
205
-
206
- ## Agent authoring guidance
207
-
208
- The server publishes the DSL contract through multiple MCP capabilities so an
209
- Agent does not need to guess syntax or inspect installed package files:
210
-
211
- | Capability | Name / URI | Purpose |
212
- | ---------- | ------------------------------------- | ---------------------------------------------------------------------------- |
213
- | Tool | `nuwax_get_openui_reference` | Returns the generated authoring guide or complete DSL JSON Schema |
214
- | Prompt | `nuwax_openui_authoring` | Starts an authoring flow with a `basic`, `dashboard`, `form`, or `all` focus |
215
- | Resource | `nuwax://openui/schema/v0.5` | Complete JSON Schema generated from the renderer's current component library |
216
- | Resource | `nuwax://openui/authoring-guide/v0.5` | Complete generated OpenUI Lang authoring guide |
217
-
218
- `nuwax_render_openui` also includes trigger guidance and a minimal valid example
219
- in its tool description and input schema. Agents should use it when the user
220
- asks for a visual card, dashboard, chart, table, report, form, status panel, or
221
- other structured interface even if the user does not explicitly mention
222
- OpenUI.
223
-
224
- For a complex interface, call `nuwax_get_openui_reference` with `format=guide`
225
- and the closest profile before rendering. Clients that only expose MCP tools
226
- can also retrieve the complete DSL schema with `format=schema`. OpenUI Lang is
227
- assignment-based and is not XML, HTML, or JSX:
228
-
229
- ```text
230
- root = Stack([title, status])
231
- title = TextContent("Deployment", "large-heavy")
232
- status = Callout("success", "Ready", "Deployment completed successfully.")
233
- ```
234
-
235
- The JSON Schema and guide are generated from the exact OpenUI renderer library
236
- used by this package, preventing the authoring contract from drifting away from
237
- runtime behavior.
238
-
239
- ## Host integration
240
-
241
- PC Web inline supports two rendering strategies. The direct Renderer is the
242
- default because it has no local Runtime/network dependency and integrates with
243
- the existing conversation lifecycle:
244
-
245
- ```tsx
246
- <Renderer
247
- library={openuiLibrary}
248
- response={artifact.document.source}
249
- isStreaming={false}
250
- />
251
- ```
252
-
253
- The host may explicitly select iframe mode for isolation or page-runtime
254
- testing:
255
-
256
- ```tsx
257
- <ToolProcessShell header={existingToolBar}>
258
- <iframe
259
- src={resolveTrustedOpenUiPageUrl(artifact.artifactId)}
260
- sandbox="allow-scripts"
261
- title={artifact.title}
262
- />
263
- </ToolProcessShell>
80
+ data/{artifactId}.openui.json
264
81
  ```
265
82
 
266
- For sidecar mode, `artifact.page.url` can be used only after the host validates
267
- its origin and artifact context. Use a dedicated restrictive iframe sandbox;
268
- do not reuse a broad legacy sandbox profile.
269
-
270
- ### Page bridge and automatic height
83
+ Pass the same `artifactId` on a later call to atomically replace the Artifact.
84
+ The original `createdAt` is retained while `updatedAt` and the document digest
85
+ are refreshed.
271
86
 
272
- After an Artifact is loaded, the Runtime sends these messages to its parent:
87
+ ## File contract
273
88
 
274
- ```ts
275
- type OpenUiRuntimeMessage =
276
- | {
277
- type: 'OPENUI_READY';
278
- protocolVersion: 'nuwax.openui-page/v1';
279
- artifactId: string;
280
- }
281
- | {
282
- type: 'OPENUI_RESIZE';
283
- protocolVersion: 'nuwax.openui-page/v1';
284
- artifactId: string;
285
- height: number;
286
- };
89
+ ```json
90
+ {
91
+ "type": "nuwax.openui-file",
92
+ "schemaVersion": "nuwax.openui-file/v1",
93
+ "artifactId": "550e8400-e29b-41d4-a716-446655440000",
94
+ "title": "Deployment status",
95
+ "presentation": { "mode": "inline", "autoOpen": false },
96
+ "document": {
97
+ "language": "openui-lang",
98
+ "specVersion": "0.5",
99
+ "source": "root = Stack([])",
100
+ "digest": "sha256:..."
101
+ },
102
+ "bindings": { "tools": [] },
103
+ "fallback": { "markdown": "" },
104
+ "createdAt": "2026-07-22T00:00:00.000Z",
105
+ "updatedAt": "2026-07-22T00:00:00.000Z"
106
+ }
287
107
  ```
288
108
 
289
- The current Runtime uses `ResizeObserver` on its rendered root, so form
290
- validation messages, date selection, expanding content, and responsive wrapping
291
- can update the iframe height. The host must validate `event.source`, the trusted
292
- Runtime origin, `protocolVersion`, and `artifactId` before applying the height.
293
- It should also clamp the height and provide an expand/scroll fallback for large
294
- documents.
295
-
296
- The v1 bridge currently reports readiness and size only. Host context, actions,
297
- nonce/sequence replay protection, and native/mobile WebView bridges are planned
298
- extensions and must not be assumed by clients yet.
299
-
300
- ### Runtime styles, validation, and locale
301
-
302
- The page Runtime imports the official `@openuidev/react-ui` stylesheet inside
303
- its own document. This prevents host rules such as `button { color: inherit }`
304
- from overriding iframe-rendered OpenUI button colors. A direct Renderer shares
305
- the host document and must load the official layered stylesheet plus scoped,
306
- unlayered compatibility overrides for host reset rules.
307
-
308
- The page Runtime replaces the built-in `required` validator to correctly accept
309
- `Date`, date-range objects, wrapped values, and selected checkbox maps. Required
310
- messages follow `navigator.language` for English, Simplified Chinese,
311
- Traditional Chinese, and Japanese. Unknown locales fall back to English. A host
312
- using the direct Renderer must install the equivalent validator integration in
313
- its own JavaScript context; Nuwax PC Web does this through its i18n runtime.
314
-
315
- ## Security model
316
-
317
- - OpenUI document size and line count are bounded.
318
- - Script-like source patterns are rejected before persistence.
319
- - Sidecar URLs are generated by this service, never accepted from the model.
320
- - The HTTP server validates `Host` to reduce DNS rebinding exposure.
321
- - Sidecar responses use CSP and no-store caching. Set
322
- `NUWAX_OPENUI_FRAME_ANCESTORS` to the exact Nuwax origins that may embed the
323
- page; the default permits only same-origin framing.
324
- - Runtime MCP bindings are explicit and cannot contain wildcards.
325
- - Mutations are data only in this contract; execution requires a separate,
326
- permission-aware host proxy.
327
-
328
- This foundation is not a substitute for authentication. A production Nuwax
329
- deployment must bind every artifact to user, tenant, conversation, message, and
330
- tool-call context.
109
+ Artifacts have no TTL. Their lifetime follows the project files and they can be
110
+ reviewed and versioned with Git.
331
111
 
332
112
  ## Development
333
113
 
334
114
  ```bash
335
- pnpm format
336
- pnpm lint
337
- pnpm typecheck
338
- pnpm test
339
- pnpm build
115
+ pnpm install
116
+ pnpm verify
340
117
  ```
341
118
 
342
- ## License
343
-
344
- Apache License 2.0. See [LICENSE](LICENSE).
119
+ The web build produces `dist/web/runtime.js` and `dist/web/runtime.css`. In the
120
+ Nuwax repository, `scripts/sync-openui-runtime.sh` copies those assets into
121
+ `public/openui-runtime/`.
@@ -1,13 +1,21 @@
1
- import type { OpenUiArtifact } from './contracts.js';
1
+ import { type OpenUiFile } from './contracts.js';
2
2
  export interface ArtifactStore {
3
- get(artifactId: string): Promise<OpenUiArtifact | undefined>;
4
- put(artifact: OpenUiArtifact): Promise<void>;
3
+ initialize(): Promise<void>;
4
+ get(artifactId: string): Promise<OpenUiFile | undefined>;
5
+ put(artifact: OpenUiFile): Promise<'created' | 'updated'>;
5
6
  delete(artifactId: string): Promise<boolean>;
6
7
  }
7
- export declare class InMemoryArtifactStore implements ArtifactStore {
8
+ export type ProjectRootResolver = () => Promise<string>;
9
+ export declare class FileArtifactStore implements ArtifactStore {
10
+ private readonly resolveProjectRoot;
8
11
  private readonly artifacts;
9
- get(artifactId: string): Promise<OpenUiArtifact | undefined>;
10
- put(artifact: OpenUiArtifact): Promise<void>;
12
+ private initialization;
13
+ constructor(resolveProjectRoot: ProjectRootResolver);
14
+ initialize(): Promise<void>;
15
+ get(artifactId: string): Promise<OpenUiFile | undefined>;
16
+ put(artifact: OpenUiFile): Promise<'created' | 'updated'>;
11
17
  delete(artifactId: string): Promise<boolean>;
18
+ private getDataDir;
19
+ private getArtifactPath;
12
20
  }
13
21
  //# sourceMappingURL=artifact-store.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"artifact-store.d.ts","sourceRoot":"","sources":["../../src/artifact-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAErD,MAAM,WAAW,aAAa;IAC5B,GAAG,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAAC;IAC7D,GAAG,CAAC,QAAQ,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC9C;AAED,qBAAa,qBAAsB,YAAW,aAAa;IACzD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAqC;IAEzD,GAAG,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC;IAY5D,GAAG,CAAC,QAAQ,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAI5C,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAGnD"}
1
+ {"version":3,"file":"artifact-store.d.ts","sourceRoot":"","sources":["../../src/artifact-store.ts"],"names":[],"mappings":"AAWA,OAAO,EAAoB,KAAK,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAEnE,MAAM,WAAW,aAAa;IAC5B,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,GAAG,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAAC;IACzD,GAAG,CAAC,QAAQ,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC,CAAC;IAC1D,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC9C;AAED,MAAM,MAAM,mBAAmB,GAAG,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;AAIxD,qBAAa,iBAAkB,YAAW,aAAa;IAIzC,OAAO,CAAC,QAAQ,CAAC,kBAAkB;IAH/C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAiC;IAC3D,OAAO,CAAC,cAAc,CAA4B;gBAErB,kBAAkB,EAAE,mBAAmB;IAE9D,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IA2B3B,GAAG,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC;IAkBxD,GAAG,CAAC,QAAQ,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC;IAmBzD,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;YAOpC,UAAU;YAIV,eAAe;CAI9B"}
@@ -1,20 +1,84 @@
1
- export class InMemoryArtifactStore {
1
+ import { mkdir, readdir, readFile, rename, rm, writeFile, } from 'node:fs/promises';
2
+ import { randomUUID } from 'node:crypto';
3
+ import path from 'node:path';
4
+ import { openUiFileSchema } from './contracts.js';
5
+ const ARTIFACT_FILE_PATTERN = /^([0-9a-f-]{36})\.openui\.json$/;
6
+ export class FileArtifactStore {
7
+ resolveProjectRoot;
2
8
  artifacts = new Map();
9
+ initialization;
10
+ constructor(resolveProjectRoot) {
11
+ this.resolveProjectRoot = resolveProjectRoot;
12
+ }
13
+ async initialize() {
14
+ this.initialization ??= (async () => {
15
+ const dataDir = await this.getDataDir();
16
+ await mkdir(dataDir, { recursive: true });
17
+ const files = await readdir(dataDir, { withFileTypes: true });
18
+ await Promise.all(files.map(async (entry) => {
19
+ if (!entry.isFile() || !ARTIFACT_FILE_PATTERN.test(entry.name))
20
+ return;
21
+ try {
22
+ const value = JSON.parse(await readFile(path.join(dataDir, entry.name), 'utf8'));
23
+ const artifact = openUiFileSchema.parse(value);
24
+ if (entry.name !== `${artifact.artifactId}.openui.json`)
25
+ return;
26
+ this.artifacts.set(artifact.artifactId, artifact);
27
+ }
28
+ catch (error) {
29
+ process.stderr.write(`[nuwax-openui] Skipping invalid artifact ${entry.name}: ${error instanceof Error ? error.message : String(error)}\n`);
30
+ }
31
+ }));
32
+ })();
33
+ return this.initialization;
34
+ }
3
35
  async get(artifactId) {
4
- const artifact = this.artifacts.get(artifactId);
5
- if (!artifact)
6
- return undefined;
7
- if (Date.parse(artifact.expiresAt) <= Date.now()) {
8
- this.artifacts.delete(artifactId);
9
- return undefined;
36
+ await this.initialize();
37
+ const cached = this.artifacts.get(artifactId);
38
+ if (cached)
39
+ return structuredClone(cached);
40
+ try {
41
+ const raw = await readFile(await this.getArtifactPath(artifactId), 'utf8');
42
+ const artifact = openUiFileSchema.parse(JSON.parse(raw));
43
+ this.artifacts.set(artifactId, artifact);
44
+ return structuredClone(artifact);
45
+ }
46
+ catch (error) {
47
+ if (error.code === 'ENOENT')
48
+ return undefined;
49
+ throw error;
10
50
  }
11
- return structuredClone(artifact);
12
51
  }
13
52
  async put(artifact) {
53
+ await this.initialize();
54
+ const validated = openUiFileSchema.parse(artifact);
55
+ const destination = await this.getArtifactPath(validated.artifactId);
56
+ const existed = Boolean(await this.get(validated.artifactId));
57
+ const temporary = `${destination}.${process.pid}.${randomUUID()}.tmp`;
58
+ await mkdir(path.dirname(destination), { recursive: true });
59
+ await writeFile(temporary, `${JSON.stringify(validated, null, 2)}\n`, {
60
+ encoding: 'utf8',
61
+ flag: 'wx',
62
+ });
63
+ await rename(temporary, destination).catch(async (error) => {
64
+ await rm(temporary, { force: true });
65
+ throw error;
66
+ });
14
67
  this.artifacts.set(artifact.artifactId, structuredClone(artifact));
68
+ return existed ? 'updated' : 'created';
15
69
  }
16
70
  async delete(artifactId) {
17
- return this.artifacts.delete(artifactId);
71
+ await this.initialize();
72
+ const existed = this.artifacts.delete(artifactId);
73
+ await rm(await this.getArtifactPath(artifactId), { force: true });
74
+ return existed;
75
+ }
76
+ async getDataDir() {
77
+ return path.join(await this.resolveProjectRoot(), 'data');
78
+ }
79
+ async getArtifactPath(artifactId) {
80
+ const parsedId = openUiFileSchema.shape.artifactId.parse(artifactId);
81
+ return path.join(await this.getDataDir(), `${parsedId}.openui.json`);
18
82
  }
19
83
  }
20
84
  //# sourceMappingURL=artifact-store.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"artifact-store.js","sourceRoot":"","sources":["../../src/artifact-store.ts"],"names":[],"mappings":"AAQA,MAAM,OAAO,qBAAqB;IACf,SAAS,GAAG,IAAI,GAAG,EAA0B,CAAC;IAE/D,KAAK,CAAC,GAAG,CAAC,UAAkB;QAC1B,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAChD,IAAI,CAAC,QAAQ;YAAE,OAAO,SAAS,CAAC;QAEhC,IAAI,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;YACjD,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;YAClC,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,OAAO,eAAe,CAAC,QAAQ,CAAC,CAAC;IACnC,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,QAAwB;QAChC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,UAAU,EAAE,eAAe,CAAC,QAAQ,CAAC,CAAC,CAAC;IACrE,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,UAAkB;QAC7B,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IAC3C,CAAC;CACF"}
1
+ {"version":3,"file":"artifact-store.js","sourceRoot":"","sources":["../../src/artifact-store.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  export declare const OPENUI_SCHEMA_VERSION: "nuwax.openui/v1";
3
+ export declare const OPENUI_FILE_SCHEMA_VERSION: "nuwax.openui-file/v1";
4
+ export declare const OPENUI_REF_SCHEMA_VERSION: "nuwax.openui-ref/v1";
3
5
  export declare const OPENUI_LANG_VERSION: "0.5";
4
6
  export declare const OPENUI_TOOL_NAME: "nuwax_render_openui";
5
7
  export declare const OPENUI_REFERENCE_TOOL_NAME: "nuwax_get_openui_reference";
@@ -27,6 +29,7 @@ declare const bindingSchema: z.ZodObject<{
27
29
  }>;
28
30
  }, z.core.$strip>;
29
31
  export declare const renderOpenUiInputSchema: z.ZodObject<{
32
+ artifactId: z.ZodOptional<z.ZodString>;
30
33
  schemaVersion: z.ZodLiteral<"nuwax.openui/v1">;
31
34
  title: z.ZodString;
32
35
  presentation: z.ZodObject<{
@@ -104,9 +107,74 @@ export declare const openUiArtifactSchema: z.ZodObject<{
104
107
  createdAt: z.ZodString;
105
108
  expiresAt: z.ZodString;
106
109
  }, z.core.$strip>;
110
+ export declare const openUiFileSchema: z.ZodObject<{
111
+ type: z.ZodLiteral<"nuwax.openui-file">;
112
+ schemaVersion: z.ZodLiteral<"nuwax.openui-file/v1">;
113
+ artifactId: z.ZodString;
114
+ title: z.ZodString;
115
+ presentation: z.ZodObject<{
116
+ mode: z.ZodEnum<{
117
+ inline: "inline";
118
+ sidecar: "sidecar";
119
+ }>;
120
+ autoOpen: z.ZodBoolean;
121
+ preferredWidth: z.ZodOptional<z.ZodEnum<{
122
+ compact: "compact";
123
+ normal: "normal";
124
+ wide: "wide";
125
+ }>>;
126
+ }, z.core.$strip>;
127
+ document: z.ZodObject<{
128
+ language: z.ZodLiteral<"openui-lang">;
129
+ specVersion: z.ZodLiteral<"0.5">;
130
+ source: z.ZodString;
131
+ digest: z.ZodString;
132
+ }, z.core.$strip>;
133
+ bindings: z.ZodObject<{
134
+ tools: z.ZodArray<z.ZodObject<{
135
+ serverId: z.ZodString;
136
+ toolName: z.ZodString;
137
+ access: z.ZodEnum<{
138
+ query: "query";
139
+ mutation: "mutation";
140
+ }>;
141
+ }, z.core.$strip>>;
142
+ }, z.core.$strip>;
143
+ fallback: z.ZodObject<{
144
+ markdown: z.ZodString;
145
+ }, z.core.$strip>;
146
+ createdAt: z.ZodString;
147
+ updatedAt: z.ZodString;
148
+ }, z.core.$strip>;
149
+ export declare const openUiArtifactRefSchema: z.ZodObject<{
150
+ type: z.ZodLiteral<"nuwax.openui-ref">;
151
+ schemaVersion: z.ZodLiteral<"nuwax.openui-ref/v1">;
152
+ artifactId: z.ZodString;
153
+ path: z.ZodString;
154
+ title: z.ZodString;
155
+ presentation: z.ZodObject<{
156
+ mode: z.ZodEnum<{
157
+ inline: "inline";
158
+ sidecar: "sidecar";
159
+ }>;
160
+ autoOpen: z.ZodBoolean;
161
+ preferredWidth: z.ZodOptional<z.ZodEnum<{
162
+ compact: "compact";
163
+ normal: "normal";
164
+ wide: "wide";
165
+ }>>;
166
+ }, z.core.$strip>;
167
+ digest: z.ZodString;
168
+ operation: z.ZodEnum<{
169
+ created: "created";
170
+ updated: "updated";
171
+ }>;
172
+ }, z.core.$strip>;
107
173
  export type RenderOpenUiInput = z.infer<typeof renderOpenUiInputSchema>;
108
174
  export type OpenUiReferenceInput = z.infer<typeof openUiReferenceInputSchema>;
109
175
  export type OpenUiArtifact = z.infer<typeof openUiArtifactSchema>;
176
+ export type OpenUiFile = z.infer<typeof openUiFileSchema>;
177
+ export type OpenUiArtifactRef = z.infer<typeof openUiArtifactRefSchema>;
110
178
  export type OpenUiBinding = z.infer<typeof bindingSchema>;
111
179
  export {};
112
180
  //# sourceMappingURL=contracts.d.ts.map