@webskill/sdk 0.2.6 → 0.2.7

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.
@@ -0,0 +1,3785 @@
1
+ import { ft as uiCatalog } from "./dist-CtBLBbEz.js";
2
+ import { n as catalogComponentImpls } from "./catalogComponents-C_V39rbF-B94i0fW7.js";
3
+ import { z } from "zod";
4
+ import { Component, Fragment, createContext, useCallback, useContext, useEffect, useInsertionEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+ import { object } from "zod/v4";
7
+ import * as z$1 from "zod/v4/core";
8
+
9
+ //#region ../../node_modules/.pnpm/@openuidev+lang-core@0.2.9_@modelcontextprotocol+sdk@1.29.0_zod@4.4.3__zod@4.4.3/node_modules/@openuidev/lang-core/dist/index.mjs
10
+ /** Resolve a field path on an object. Supports dot-paths: "state.name" → obj.state.name */
11
+ function resolveField(obj, path) {
12
+ if (!path || obj == null) return void 0;
13
+ if (!path.includes(".")) return obj[path];
14
+ let cur = obj;
15
+ for (const p of path.split(".")) {
16
+ if (cur == null) return void 0;
17
+ cur = cur[p];
18
+ }
19
+ return cur;
20
+ }
21
+ function toNumber(val) {
22
+ if (typeof val === "number") return val;
23
+ if (typeof val === "string") {
24
+ const n = Number(val);
25
+ return isNaN(n) ? 0 : n;
26
+ }
27
+ if (typeof val === "boolean") return val ? 1 : 0;
28
+ return 0;
29
+ }
30
+ const BUILTINS = {
31
+ Count: {
32
+ name: "Count",
33
+ signature: "Count(array) → number",
34
+ description: "Returns array length",
35
+ fn: (arr) => Array.isArray(arr) ? arr.length : 0
36
+ },
37
+ First: {
38
+ name: "First",
39
+ signature: "First(array) → element",
40
+ description: "Returns first element of array",
41
+ fn: (arr) => Array.isArray(arr) ? arr[0] ?? null : null
42
+ },
43
+ Last: {
44
+ name: "Last",
45
+ signature: "Last(array) → element",
46
+ description: "Returns last element of array",
47
+ fn: (arr) => Array.isArray(arr) ? arr[arr.length - 1] ?? null : null
48
+ },
49
+ Sum: {
50
+ name: "Sum",
51
+ signature: "Sum(numbers[]) → number",
52
+ description: "Sum of numeric array",
53
+ fn: (arr) => Array.isArray(arr) ? arr.reduce((a, b) => a + toNumber(b), 0) : 0
54
+ },
55
+ Avg: {
56
+ name: "Avg",
57
+ signature: "Avg(numbers[]) → number",
58
+ description: "Average of numeric array",
59
+ fn: (arr) => Array.isArray(arr) && arr.length ? arr.reduce((a, b) => a + toNumber(b), 0) / arr.length : 0
60
+ },
61
+ Min: {
62
+ name: "Min",
63
+ signature: "Min(numbers[]) → number",
64
+ description: "Minimum value in array",
65
+ fn: (arr) => Array.isArray(arr) && arr.length ? arr.reduce((acc, b) => Math.min(acc, toNumber(b)), toNumber(arr[0])) : 0
66
+ },
67
+ Max: {
68
+ name: "Max",
69
+ signature: "Max(numbers[]) → number",
70
+ description: "Maximum value in array",
71
+ fn: (arr) => Array.isArray(arr) && arr.length ? arr.reduce((acc, b) => Math.max(acc, toNumber(b)), toNumber(arr[0])) : 0
72
+ },
73
+ Sort: {
74
+ name: "Sort",
75
+ signature: "Sort(array, field, direction?) → sorted array",
76
+ description: "Sort array by field. Direction: \"asc\" (default) or \"desc\"",
77
+ fn: (arr, field, dir) => {
78
+ if (!Array.isArray(arr)) return arr;
79
+ const f = String(field ?? "");
80
+ const desc = String(dir ?? "asc") === "desc";
81
+ return [...arr].sort((a, b) => {
82
+ const av = f ? resolveField(a, f) : a;
83
+ const bv = f ? resolveField(b, f) : b;
84
+ const aIsNumeric = typeof av === "number" || typeof av === "string" && !isNaN(Number(av)) && av !== "";
85
+ const bIsNumeric = typeof bv === "number" || typeof bv === "string" && !isNaN(Number(bv)) && bv !== "";
86
+ if (aIsNumeric && bIsNumeric) {
87
+ const diff = toNumber(av) - toNumber(bv);
88
+ return desc ? -diff : diff;
89
+ }
90
+ const cmp = String(av ?? "").localeCompare(String(bv ?? ""));
91
+ return desc ? -cmp : cmp;
92
+ });
93
+ }
94
+ },
95
+ Filter: {
96
+ name: "Filter",
97
+ signature: "Filter(array, field, operator: \"==\" | \"!=\" | \">\" | \"<\" | \">=\" | \"<=\" | \"contains\", value) → filtered array",
98
+ description: "Filter array by field value",
99
+ fn: (arr, field, op, value) => {
100
+ if (!Array.isArray(arr)) return [];
101
+ const f = String(field ?? "");
102
+ const o = String(op ?? "==");
103
+ return arr.filter((item) => {
104
+ const v = f ? resolveField(item, f) : item;
105
+ switch (o) {
106
+ case "==": return v == value;
107
+ case "!=": return v != value;
108
+ case ">": return toNumber(v) > toNumber(value);
109
+ case "<": return toNumber(v) < toNumber(value);
110
+ case ">=": return toNumber(v) >= toNumber(value);
111
+ case "<=": return toNumber(v) <= toNumber(value);
112
+ case "contains": return String(v ?? "").includes(String(value ?? ""));
113
+ default: return false;
114
+ }
115
+ });
116
+ }
117
+ },
118
+ Round: {
119
+ name: "Round",
120
+ signature: "Round(number, decimals?) → number",
121
+ description: "Round to N decimal places (default 0)",
122
+ fn: (n, decimals) => {
123
+ const num = toNumber(n);
124
+ const d = decimals != null ? toNumber(decimals) : 0;
125
+ const factor = Math.pow(10, d);
126
+ return Math.round(num * factor) / factor;
127
+ }
128
+ },
129
+ Abs: {
130
+ name: "Abs",
131
+ signature: "Abs(number) → number",
132
+ description: "Absolute value",
133
+ fn: (n) => Math.abs(toNumber(n))
134
+ },
135
+ Floor: {
136
+ name: "Floor",
137
+ signature: "Floor(number) → number",
138
+ description: "Round down to nearest integer",
139
+ fn: (n) => Math.floor(toNumber(n))
140
+ },
141
+ Ceil: {
142
+ name: "Ceil",
143
+ signature: "Ceil(number) → number",
144
+ description: "Round up to nearest integer",
145
+ fn: (n) => Math.ceil(toNumber(n))
146
+ }
147
+ };
148
+ /**
149
+ * Lazy builtins — these receive AST nodes (not evaluated values) and
150
+ * control their own evaluation. Handled specially in evaluator.ts.
151
+ */
152
+ const LAZY_BUILTINS = /* @__PURE__ */ new Set(["Each"]);
153
+ const LAZY_BUILTIN_DEFS = { Each: {
154
+ signature: "Each(array, varName, template)",
155
+ description: "Evaluate template for each element. varName is the loop variable — use it ONLY inside the template expression (inline). Do NOT create a separate statement for the template."
156
+ } };
157
+ /** Maps parser-level action step names → runtime step type values. Single source of truth. */
158
+ const ACTION_STEPS = {
159
+ Run: "run",
160
+ ToAssistant: "continue_conversation",
161
+ OpenUrl: "open_url",
162
+ Set: "set",
163
+ Reset: "reset"
164
+ };
165
+ /** All action expression names (steps + the Action container) */
166
+ const ACTION_NAMES = /* @__PURE__ */ new Set(["Action", ...Object.keys(ACTION_STEPS)]);
167
+ /** Set of builtin names for fast lookup (includes action expressions) */
168
+ const BUILTIN_NAMES = /* @__PURE__ */ new Set([
169
+ ...Object.keys(BUILTINS),
170
+ ...LAZY_BUILTINS,
171
+ ...ACTION_NAMES
172
+ ]);
173
+ /** Check if a name is a builtin function (not a component) */
174
+ function isBuiltin(name) {
175
+ return BUILTIN_NAMES.has(name);
176
+ }
177
+ /** Reserved statement-level call names — not builtins, not components */
178
+ const RESERVED_CALLS = {
179
+ Query: "Query",
180
+ Mutation: "Mutation"
181
+ };
182
+ /** Check if a name is a reserved statement call (Query, Mutation) */
183
+ function isReservedCall(name) {
184
+ return name in RESERVED_CALLS;
185
+ }
186
+ function jsonSchemaTypeStr(schema) {
187
+ const type = schema.type;
188
+ if (type === "string") {
189
+ const enumVals = schema.enum;
190
+ if (enumVals) return enumVals.map((v) => `"${v}"`).join(" | ");
191
+ return "string";
192
+ }
193
+ if (type === "number" || type === "integer") return "number";
194
+ if (type === "boolean") return "boolean";
195
+ if (type === "array") {
196
+ const items = schema.items;
197
+ if (items) return `${jsonSchemaTypeStr(items)}[]`;
198
+ return "any[]";
199
+ }
200
+ if (type === "object") {
201
+ const props = schema.properties;
202
+ if (props && Object.keys(props).length > 0) {
203
+ const required = schema.required ?? [];
204
+ return `{${Object.entries(props).map(([k, v]) => {
205
+ return `${k}${required.includes(k) ? "" : "?"}: ${jsonSchemaTypeStr(v)}`;
206
+ }).join(", ")}}`;
207
+ }
208
+ return "object";
209
+ }
210
+ return "any";
211
+ }
212
+ /** Generate a default-values hint object for an output schema. */
213
+ function defaultForSchema(schema) {
214
+ const type = schema.type;
215
+ if (type === "string") return "";
216
+ if (type === "number" || type === "integer") return 0;
217
+ if (type === "boolean") return false;
218
+ if (type === "array") return [];
219
+ if (type === "object") {
220
+ const props = schema.properties;
221
+ if (props && Object.keys(props).length > 0) {
222
+ const result = {};
223
+ for (const [k, v] of Object.entries(props)) result[k] = defaultForSchema(v);
224
+ return result;
225
+ }
226
+ return {};
227
+ }
228
+ return null;
229
+ }
230
+ const PREAMBLE = `You are an AI assistant that responds using openui-lang, a declarative UI language. Your ENTIRE response must be valid openui-lang code — no markdown, no explanations, just openui-lang.`;
231
+ function syntaxRules(rootName, flags) {
232
+ const lines = [
233
+ "## Syntax Rules",
234
+ "",
235
+ "1. Each statement is on its own line: `identifier = Expression`",
236
+ `2. \`root\` is the entry point — every program must define \`root = ${rootName}(...)\``,
237
+ "3. Expressions are: strings (\"...\"), numbers, booleans (true/false), null, arrays ([...]), objects ({...}), or component calls TypeName(arg1, arg2, ...)",
238
+ "4. Use references for readability: define `name = ...` on one line, then use `name` later",
239
+ "5. EVERY variable (except root) MUST be referenced by at least one other variable. Unreferenced variables are silently dropped and will NOT render. Always include defined variables in their parent's children/items array.",
240
+ "6. Arguments are POSITIONAL (order matters, not names). Write `Stack([children], \"row\", \"l\")` NOT `Stack([children], direction: \"row\", gap: \"l\")` — colon syntax is NOT supported and silently breaks",
241
+ "7. Optional arguments can be omitted from the end"
242
+ ];
243
+ let ruleNum = 8;
244
+ if (flags.bindings) lines.push(`${ruleNum++}. Declare mutable state with \`$varName = defaultValue\`. Components marked with \`$binding\` can read/write these. Undeclared $variables are auto-created with null default.`);
245
+ if (flags.supportsExpressions) lines.push(`${ruleNum++}. String concatenation: \`"text" + $var + "more"\``, `${ruleNum++}. Dot member access: \`query.field\` reads a field; on arrays it extracts that field from every element`, `${ruleNum++}. Index access: \`arr[0]\`, \`data[index]\``, `${ruleNum++}. Arithmetic operators: +, -, *, /, % (work on numbers; + is string concat when either side is a string)`, `${ruleNum++}. Comparison: ==, !=, >, <, >=, <=`, `${ruleNum++}. Logical: &&, ||, ! (prefix)`, `${ruleNum++}. Ternary: \`condition ? valueIfTrue : valueIfFalse\``, `${ruleNum++}. Parentheses for grouping: \`(a + b) * c\``);
246
+ lines.push("- Strings use double quotes with backslash escaping");
247
+ return lines.join("\n");
248
+ }
249
+ function builtinFunctionsSection() {
250
+ const builtinLines = Object.values(BUILTINS).map((b) => `@${b.signature} — ${b.description}`);
251
+ const lazyLines = Object.values(LAZY_BUILTIN_DEFS).map((b) => `@${b.signature} — ${b.description}`);
252
+ return `## Built-in Functions
253
+
254
+ Data functions prefixed with \`@\` to distinguish from components. These are the ONLY functions available — do NOT invent new ones.
255
+ Use @-prefixed built-in functions (@Count, @Sum, @Avg, @Min, @Max, @Round) on Query results — do NOT hardcode computed values.
256
+
257
+ ${[...builtinLines, ...lazyLines].join("\n")}
258
+
259
+ Builtins compose — output of one is input to the next:
260
+ \`@Count(@Filter(data.rows, "field", "==", "val"))\` for KPIs/chart values, \`@Round(@Avg(data.rows.score), 1)\`, \`@Each(data.rows, "item", Comp(item.field))\` for per-item rendering.
261
+ Array pluck: \`data.rows.field\` extracts a field from every row → use with @Sum, @Avg, charts, tables.
262
+
263
+ IMPORTANT @Each rule: The loop variable (e.g. "item") is ONLY available inside the @Each template expression. Always inline the template — do NOT extract it to a separate statement.
264
+ CORRECT: \`Col("Actions", @Each(rows, "t", Button("Edit", Action([@Set($id, t.id)]))))\`
265
+ WRONG: \`myBtn = Button("Edit", Action([@Set($id, t.id)]))\` then \`Col("Actions", @Each(rows, "t", myBtn))\` — t is undefined in myBtn.`;
266
+ }
267
+ function querySection() {
268
+ return `## Query — Live Data Fetching
269
+
270
+ Fetch data from available tools. Returns defaults instantly, swaps in real data when it arrives.
271
+
272
+ \`\`\`
273
+ metrics = Query("tool_name", {arg1: value, arg2: $binding}, {defaultField: 0, defaultData: []}, refreshInterval?)
274
+ \`\`\`
275
+
276
+ - First arg: tool name (string)
277
+ - Second arg: arguments object (may reference $bindings — re-fetches automatically on change)
278
+ - Third arg: default data (rendered immediately before fetch resolves)
279
+ - Fourth arg (optional): refresh interval in seconds (e.g. 30 for auto-refresh every 30s)
280
+ - Use dot access on results: metrics.totalEvents, metrics.data.day (array pluck)
281
+ - Query results must use regular identifiers: \`metrics = Query(...)\`, NOT \`$metrics = Query(...)\`
282
+ - Manual refresh: \`Button("Refresh", Action([@Run(query1), @Run(query2)]), "secondary")\` — re-fetches the listed queries
283
+ - Refresh all queries: create Action with @Run for each query`;
284
+ }
285
+ function mutationSection() {
286
+ return `## Mutation — Write Operations
287
+
288
+ Execute state-changing tool calls (create, update, delete). Unlike Query (auto-fetches on render), Mutation fires only on button click via Action.
289
+
290
+ \`\`\`
291
+ result = Mutation("tool_name", {arg1: $binding, arg2: "value"})
292
+ \`\`\`
293
+
294
+ - First arg: tool name (string)
295
+ - Second arg: arguments object (evaluated with current $binding values at click time)
296
+ - result.status: "idle" | "loading" | "success" | "error"
297
+ - result.data: tool response on success
298
+ - result.error: error message on failure
299
+ - Mutation results use regular identifiers: \`result = Mutation(...)\`, NOT \`$result\`
300
+ - Show loading state: \`result.status == "loading" ? TextContent("Saving...") : null\``;
301
+ }
302
+ function actionSection(flags) {
303
+ const steps = ["- @ToAssistant(\"message\") — Send a message to the assistant (for conversational buttons like \"Tell me more\", \"Explain this\")", "- @OpenUrl(\"https://...\") — Navigate to a URL"];
304
+ if (flags.bindings) steps.push("- @Set($variable, value) — Set a $variable to a specific value", "- @Reset($var1, $var2, ...) — Reset $variables to their declared defaults (e.g. @Reset($title, $priority) restores $title=\"\" and $priority=\"medium\")");
305
+ if (flags.toolCalls) steps.unshift("- @Run(queryOrMutationRef) — Execute a Mutation or re-fetch a Query (ref must be a declared Query/Mutation)");
306
+ const examples = [];
307
+ if (flags.toolCalls) examples.push(`Example — mutation + refresh + reset (PREFERRED pattern):
308
+ \`\`\`
309
+ $binding = "default"
310
+ result = Mutation("tool_name", {field: $binding})
311
+ data = Query("tool_name", {}, {rows: []})
312
+ onSubmit = Action([@Run(result), @Run(data), @Reset($binding)])
313
+ \`\`\``);
314
+ examples.push(`Example — simple nav:
315
+ \`\`\`
316
+ viewBtn = Button("View", Action([@OpenUrl("https://example.com")]))
317
+ \`\`\``);
318
+ const rules = ["- Action can be assigned to a variable or inlined: Button(\"Go\", onSubmit) and Button(\"Go\", Action([...])) both work"];
319
+ if (flags.toolCalls) rules.push("- If a @Run(mutation) step fails, remaining steps are skipped (halt on failure)", "- @Run(queryRef) re-fetches the query (fire-and-forget, cannot fail)");
320
+ return `## Action — Button Behavior
321
+
322
+ Action([@steps...]) wires button clicks to operations. Steps are @-prefixed built-in actions. Steps execute in order.
323
+ Buttons without an explicit Action prop automatically send their label to the assistant (equivalent to Action([@ToAssistant(label)])).
324
+
325
+ Available steps:
326
+ ${steps.join("\n")}
327
+
328
+ ${examples.join("\n\n")}
329
+
330
+ ${rules.join("\n")}`;
331
+ }
332
+ function interactiveFiltersSection() {
333
+ return `## Interactive Filters
334
+
335
+ To let the user filter data with a dropdown:
336
+ 1. Declare a $variable with a default: \`$dateRange = "14"\`
337
+ 2. Create a Select with name, items, and binding: \`Select("dateRange", [SelectItem("7", "Last 7 days"), ...], null, null, $dateRange)\`
338
+ 3. Wrap in FormControl for a label: \`FormControl("Date Range", Select(...))\`
339
+ 4. Pass $dateRange in Query args: \`Query("tool", {dateRange: $dateRange}, {defaults})\`
340
+ 5. When the user changes the Select, $dateRange updates and the Query automatically re-fetches
341
+
342
+ FILTER WIRING RULE: If a $binding filter is visible in the UI, EVERY relevant Query MUST reference that $binding in its args. Never show a filter dropdown while hardcoding the query args.
343
+
344
+ Rules for $variables:
345
+ - $variables hold simple values (strings or numbers), NOT arrays or objects
346
+ - $variables must be bound to a Select/Input component via the value argument (last positional arg) to be interactive
347
+ - Queries must use regular identifiers (NOT $variables): \`metrics = Query(...)\` not \`$metrics = Query(...)\`
348
+ - **Auto-declare**: You do NOT need to explicitly declare $variables. If you use \`$foo\` without declaring it, the parser auto-creates \`$foo = null\`. You can still declare explicitly to set a default: \`$days = "14"\`
349
+
350
+ ## Forms
351
+
352
+ Simple form — no $bindings needed. Field values are managed internally by the Form via the name prop:
353
+ \`\`\`
354
+ contactForm = Form("contact", submitBtn, [nameField, emailField])
355
+ nameField = FormControl("Name", Input("name", "Your name", "text", {required: true}))
356
+ emailField = FormControl("Email", Input("email", "your@email.com", "email", {required: true, email: true}))
357
+ submitBtn = Button("Submit")
358
+ \`\`\`
359
+
360
+ Use $bindings when you need to read field values elsewhere (in Action context, Query args, or conditionals). They are auto-declared:
361
+ \`\`\`
362
+ $role = "engineer"
363
+ contactForm = Form("contact", submitBtn, [nameField, emailField, roleField])
364
+ nameField = FormControl("Name", Input("name", "Enter your name", "text", {required: true}, $name))
365
+ emailField = FormControl("Email", Input("email", "Enter your email", "email", {required: true, email: true}, $email))
366
+ roleField = FormControl("Role", Select("role", [SelectItem("engineer", "Engineer"), SelectItem("designer", "Designer"), SelectItem("pm", "PM")], null, {required: true}, $role))
367
+ submitBtn = Button("Submit")
368
+ \`\`\`
369
+
370
+ For form + mutation patterns (create, refresh, reset), see the Action section example above.
371
+
372
+ IMPORTANT: Always add validation rules to form fields used with Mutations. Use OBJECT syntax: {required: true, email: true, minLength: 8}. The renderer shows error messages automatically and blocks submit when validation fails.`;
373
+ }
374
+ function editModeSection() {
375
+ return `## Edit Mode
376
+
377
+ The runtime merges by statement name: same name = replace, new name = append.
378
+ Output ONLY statements that changed or are new. Everything else is kept automatically.
379
+
380
+ ### Delete
381
+ To remove a component, update the parent to exclude it from its children array. Orphaned statements are automatically garbage-collected.
382
+ Example — remove chart: \`root = Stack([header, kpiRow, table])\` — chart is no longer in the children list, so it and any statements only it referenced are auto-deleted.
383
+
384
+ ### Patch size guide
385
+ - Changing a title or label: 1 statement
386
+ - Adding a component: 2-3 statements (the new component + parent update)
387
+ - Removing a component: 1 statement (re-declare parent without the removed child)
388
+ - Adding a filter + wiring to query: 3-5 statements
389
+ - Restructuring into tabs: 5-10 statements
390
+
391
+ ### Rules
392
+ - Reuse existing statement names exactly — do not rename
393
+ - Do NOT re-emit unchanged statements — the runtime keeps them
394
+ - A typical edit patch is 1-10 statements, not 20+
395
+ - If the existing code already satisfies the request, output only the root statement
396
+ - NEVER output the entire program as a patch. Only output what actually changes
397
+ - If you are about to output more than 10 statements, reconsider — most edits need fewer`;
398
+ }
399
+ function streamingRules(rootName, flags) {
400
+ const steps = [`1. \`root = ${rootName}(...)\` — UI shell appears immediately`];
401
+ if (flags.supportsExpressions) {
402
+ steps.push("2. $variable declarations — state ready for bindings");
403
+ steps.push("3. Query statements — defaults resolve immediately so components render with data");
404
+ steps.push("4. Component definitions — fill in with data already available");
405
+ steps.push("5. Data values — leaf content last");
406
+ } else {
407
+ steps.push("2. Component definitions — fill in as they stream");
408
+ steps.push("3. Data values — leaf content last");
409
+ }
410
+ return `## Hoisting & Streaming (CRITICAL)
411
+
412
+ openui-lang supports hoisting: a reference can be used BEFORE it is defined. The parser resolves all references after the full input is parsed.
413
+
414
+ During streaming, the output is re-parsed on every chunk. Undefined references are temporarily unresolved and appear once their definitions stream in. This creates a progressive top-down reveal — structure first, then data fills in.
415
+
416
+ **Recommended statement order for optimal streaming:**
417
+ ${steps.join("\n")}
418
+
419
+ Always write the root = ${rootName}(...) statement first so the UI shell appears immediately, even before child data has streamed in.`;
420
+ }
421
+ function inlineModeSection() {
422
+ return `## Inline Mode
423
+
424
+ You are in inline mode. You can respond in two ways:
425
+
426
+ ### 1. Code response (when the user wants to CREATE or CHANGE the UI)
427
+ Wrap openui-lang code in triple-backtick fences. You can include explanatory text before/after:
428
+
429
+ Here's your dashboard:
430
+
431
+ \`\`\`openui-lang
432
+ root = RootComp([header, content])
433
+ header = SomeHeader("Title")
434
+ content = SomeContent("Hello world")
435
+ \`\`\`
436
+
437
+ I created a simple layout with a header.
438
+
439
+ ### 2. Text-only response (when the user asks a QUESTION)
440
+ If the user asks "what is this?", "explain the chart", "how does this work", etc. — respond with plain text. Do NOT output any openui-lang code. The existing dashboard stays unchanged.
441
+
442
+ ### Rules
443
+ - When the user asks for changes, output ONLY the changed/new statements in a fenced block
444
+ - When the user asks a question, respond with text only — NO code. The dashboard stays unchanged.
445
+ - The parser extracts code from fences automatically. Text outside fences is shown as chat.`;
446
+ }
447
+ function toolWorkflowSection() {
448
+ return `## Data Workflow
449
+
450
+ When tools are available, follow this workflow:
451
+ 1. FIRST: Call the most relevant tool to inspect the real data shape before generating code
452
+ 2. Use Query() for READ operations (data that should stay live) — NEVER hardcode tool results as literal arrays or objects
453
+ 3. Use Mutation() for WRITE operations (create, update, delete) — triggered by button clicks via Action([@Run(mutationRef)])
454
+ 4. Use the real data from step 1 as condensed Query defaults (3-5 rows) so the UI renders immediately
455
+ 5. Use @-prefixed builtins (@Count, @Filter, @Sort, @Sum) on Query results for KPIs and aggregations — the runtime evaluates these live on every refresh
456
+ 6. Hardcoded arrays are ONLY for static display data (labels, options) where no tool exists
457
+
458
+ WRONG — you called a tool and got data back, but you inlined the results:
459
+ \`\`\`
460
+ openCount = 2
461
+ item1 = SomeComp("first item title")
462
+ item2 = SomeComp("second item title")
463
+ list = Stack([item1, item2])
464
+ chart = SomeChart(["A", "B"], [12, 8])
465
+ \`\`\`
466
+ This is static — it shows stale data and won't update. Creating item1, item2, item3... manually is ALWAYS wrong when a tool exists.
467
+
468
+ RIGHT — use Query() for live data, Mutation() for writes, @builtins to derive values:
469
+ \`\`\`
470
+ data = Query("tool_name", {}, {rows: []})
471
+ openCount = @Count(@Filter(data.rows, "field", "==", "value"))
472
+ list = @Each(data.rows, "item", SomeComp(item.title, item.field))
473
+ createResult = Mutation("create_tool", {title: $title})
474
+ submitBtn = Button("Create", Action([@Run(createResult), @Run(data), @Reset($title)]))
475
+ \`\`\`
476
+ Everything derives from the Query — when data refreshes, the entire dashboard updates automatically.`;
477
+ }
478
+ function importantRules(rootName, flags) {
479
+ const verifyLines = [`1. root = ${rootName}(...) is the FIRST line (for optimal streaming).`, "2. Every referenced name is defined. Every defined name (other than root) is reachable from root."];
480
+ if (flags.toolCalls) verifyLines.push("3. Every Query result is referenced by at least one component.");
481
+ if (flags.bindings) verifyLines.push(`${flags.toolCalls ? "4" : "3"}. Every $binding appears in at least one component or expression.`);
482
+ return `## Important Rules
483
+ - When asked about data, generate realistic/plausible data
484
+ - Choose components that best represent the content (tables for comparisons, charts for trends, forms for input, etc.)
485
+
486
+ ## Final Verification
487
+ Before finishing, walk your output and verify:
488
+ ${verifyLines.join("\n")}`;
489
+ }
490
+ function renderToolSignature(tool) {
491
+ let args = "";
492
+ if (tool.inputSchema) {
493
+ const props = tool.inputSchema.properties;
494
+ const required = tool.inputSchema.required ?? [];
495
+ if (props && Object.keys(props).length > 0) args = Object.entries(props).map(([k, v]) => {
496
+ return `${k}${required.includes(k) ? "" : "?"}: ${jsonSchemaTypeStr(v)}`;
497
+ }).join(", ");
498
+ }
499
+ let returnType = "";
500
+ if (tool.outputSchema) returnType = ` → ${jsonSchemaTypeStr(tool.outputSchema)}`;
501
+ let line = `- ${tool.name}(${args})${returnType}`;
502
+ if (tool.description) line += `\n ${tool.description}`;
503
+ return line;
504
+ }
505
+ function renderToolsSection(tools) {
506
+ const lines = [];
507
+ const stringTools = [];
508
+ const specTools = [];
509
+ for (const tool of tools) if (typeof tool === "string") stringTools.push(tool);
510
+ else specTools.push(tool);
511
+ lines.push("## Available Tools");
512
+ lines.push("");
513
+ lines.push("Use these with Query() for read operations or Mutation() for write operations. The LLM decides which is appropriate based on the tool's purpose.");
514
+ lines.push("");
515
+ for (const t of stringTools) lines.push(`- ${t}`);
516
+ for (const t of specTools) lines.push(renderToolSignature(t));
517
+ const toolsWithOutput = specTools.filter((t) => t.outputSchema);
518
+ if (toolsWithOutput.length > 0) {
519
+ lines.push("");
520
+ lines.push("### Default values for Query results");
521
+ lines.push("");
522
+ lines.push("Use these shapes as minimal Query defaults:");
523
+ for (const t of toolsWithOutput) {
524
+ const defaults = defaultForSchema(t.outputSchema);
525
+ lines.push(`- ${t.name}: \`${JSON.stringify(defaults)}\``);
526
+ }
527
+ }
528
+ lines.push("");
529
+ lines.push("CRITICAL: Use ONLY the tools listed above in Query() and Mutation() calls. Do NOT invent or guess tool names. If the user asks for functionality that doesn't match any available tool, use realistic mock data instead of fabricating a tool call.");
530
+ return lines.join("\n");
531
+ }
532
+ function generateComponentSignatures(spec, flags) {
533
+ const lines = [
534
+ "## Component Signatures",
535
+ "",
536
+ "Arguments marked with ? are optional. Sub-components can be inline or referenced; prefer references for better streaming."
537
+ ];
538
+ if (flags.usesActionExpression) {
539
+ const allSteps = [
540
+ flags.toolCalls ? "@Run" : "",
541
+ "@ToAssistant",
542
+ "@OpenUrl",
543
+ flags.bindings ? "@Set" : "",
544
+ flags.bindings ? "@Reset" : ""
545
+ ].filter(Boolean);
546
+ lines.push(`Props typed \`ActionExpression\` accept an Action([@steps...]) expression. See the Action section for available steps (${allSteps.join(", ")}).`);
547
+ }
548
+ if (flags.bindings || Object.values(spec.components).some((c) => c.signature?.includes("$binding"))) lines.push("Props marked `$binding<type>` accept a `$variable` reference for two-way binding.");
549
+ const formatSig = (comp) => comp.description ? `${comp.signature} — ${comp.description}` : comp.signature;
550
+ if (spec.componentGroups?.length) {
551
+ const grouped = /* @__PURE__ */ new Set();
552
+ for (const group of spec.componentGroups) {
553
+ lines.push("", `### ${group.name}`);
554
+ for (const name of group.components) {
555
+ if (grouped.has(name)) continue;
556
+ const comp = spec.components[name];
557
+ if (!comp) continue;
558
+ grouped.add(name);
559
+ lines.push(formatSig(comp));
560
+ }
561
+ if (group.notes?.length) for (const note of group.notes) lines.push(note);
562
+ }
563
+ const ungrouped = Object.keys(spec.components).filter((n) => !grouped.has(n));
564
+ if (ungrouped.length) {
565
+ lines.push("", "### Other");
566
+ for (const name of ungrouped) {
567
+ const comp = spec.components[name];
568
+ lines.push(formatSig(comp));
569
+ }
570
+ }
571
+ } else {
572
+ lines.push("");
573
+ for (const [, comp] of Object.entries(spec.components)) lines.push(formatSig(comp));
574
+ }
575
+ return lines.join("\n");
576
+ }
577
+ function generatePrompt(spec) {
578
+ const rootName = spec.root ?? "Root";
579
+ const hasTools = !!spec.tools?.length;
580
+ const toolCalls = spec.toolCalls ?? hasTools;
581
+ const bindings = spec.bindings ?? toolCalls;
582
+ const supportsExpressions = toolCalls || bindings;
583
+ const usesActionExpression = Object.values(spec.components).some((c) => c.signature?.includes("ActionExpression"));
584
+ const parts = [];
585
+ parts.push(spec.preamble ?? PREAMBLE);
586
+ parts.push("");
587
+ parts.push(syntaxRules(rootName, {
588
+ supportsExpressions,
589
+ bindings
590
+ }));
591
+ parts.push("");
592
+ parts.push(generateComponentSignatures(spec, {
593
+ toolCalls,
594
+ bindings,
595
+ usesActionExpression
596
+ }));
597
+ if (supportsExpressions) {
598
+ parts.push("");
599
+ parts.push(builtinFunctionsSection());
600
+ }
601
+ if (toolCalls) {
602
+ parts.push("");
603
+ parts.push(querySection());
604
+ parts.push("");
605
+ parts.push(mutationSection());
606
+ }
607
+ if (usesActionExpression) {
608
+ parts.push("");
609
+ parts.push(actionSection({
610
+ toolCalls,
611
+ bindings
612
+ }));
613
+ }
614
+ if (toolCalls && bindings) {
615
+ parts.push("");
616
+ parts.push(interactiveFiltersSection());
617
+ }
618
+ if (toolCalls) {
619
+ parts.push("");
620
+ parts.push(toolWorkflowSection());
621
+ }
622
+ if (spec.tools?.length) {
623
+ parts.push("");
624
+ parts.push(renderToolsSection(spec.tools));
625
+ }
626
+ parts.push("");
627
+ parts.push(streamingRules(rootName, { supportsExpressions }));
628
+ const allExamples = [...spec.examples ?? [], ...spec.toolExamples ?? []];
629
+ if (allExamples.length) {
630
+ parts.push("");
631
+ parts.push("## Examples");
632
+ parts.push("");
633
+ for (const ex of allExamples) {
634
+ parts.push(ex);
635
+ parts.push("");
636
+ }
637
+ }
638
+ if (spec.editMode) {
639
+ parts.push("");
640
+ parts.push(editModeSection());
641
+ }
642
+ if (spec.inlineMode) {
643
+ parts.push("");
644
+ parts.push(inlineModeSection());
645
+ }
646
+ parts.push(importantRules(rootName, {
647
+ toolCalls,
648
+ bindings
649
+ }));
650
+ if (spec.additionalRules?.length) {
651
+ parts.push("");
652
+ for (const rule of spec.additionalRules) parts.push(`- ${rule}`);
653
+ }
654
+ return parts.join("\n");
655
+ }
656
+ /** WeakSet tracks reactive schemas without mutating the schema objects. */
657
+ const reactiveSchemas = /* @__PURE__ */ new WeakSet();
658
+ /** Check if a schema was marked reactive. Used by Zod introspection for $binding<> prefix. */
659
+ function isReactiveSchema(schema) {
660
+ return typeof schema === "object" && schema !== null && reactiveSchemas.has(schema);
661
+ }
662
+ const schemaIdTags = /* @__PURE__ */ new WeakMap();
663
+ function assertV4Schema(schema, componentName) {
664
+ if (schema != null && typeof schema === "object" && "_def" in schema && !("_zod" in schema)) throw new Error(`[OpenUI] Component "${componentName}" was defined with a Zod 3 schema. OpenUI requires Zod 4 schemas. If you're on zod@3.25+, import from "zod/v4" instead of "zod". See: https://zod.dev/v4/versioning`);
665
+ }
666
+ /**
667
+ * Define a component with name, schema, description, and renderer.
668
+ * Tags the schema with the component name so it resolves in prompt
669
+ * signatures even if the component isn't in every library.
670
+ */
671
+ function defineComponent$1(config) {
672
+ assertV4Schema(config.props, config.name);
673
+ schemaIdTags.set(config.props, config.name);
674
+ return {
675
+ ...config,
676
+ ref: config.props
677
+ };
678
+ }
679
+ function getZodDef(schema) {
680
+ return schema?._zod?.def;
681
+ }
682
+ function getZodType(schema) {
683
+ return getZodDef(schema)?.type;
684
+ }
685
+ function isOptionalType(schema) {
686
+ const type = getZodType(schema);
687
+ return type === "optional" || type === "default" || type === "nullable";
688
+ }
689
+ function unwrap(schema) {
690
+ let s = schema;
691
+ let def = getZodDef(s);
692
+ while (def?.type === "optional" || def?.type === "default" || def?.type === "nullable") {
693
+ s = def.innerType;
694
+ def = getZodDef(s);
695
+ }
696
+ return s;
697
+ }
698
+ function isArrayType(schema) {
699
+ return getZodType(unwrap(schema)) === "array";
700
+ }
701
+ function getArrayInnerType(schema) {
702
+ const def = getZodDef(unwrap(schema));
703
+ if (def?.type === "array") return def.element ?? def.innerType;
704
+ }
705
+ function getEnumValues(schema) {
706
+ const def = getZodDef(unwrap(schema));
707
+ if (def?.type !== "enum") return void 0;
708
+ if (Array.isArray(def.values)) return def.values;
709
+ if (def.entries && typeof def.entries === "object") return Object.keys(def.entries);
710
+ }
711
+ function getSchemaId(schema, reg) {
712
+ try {
713
+ const meta = reg.get(schema);
714
+ if (meta?.id) return meta.id;
715
+ } catch {}
716
+ if (typeof schema === "object" && schema !== null) return schemaIdTags.get(schema);
717
+ }
718
+ function getUnionOptions(schema) {
719
+ const def = getZodDef(schema);
720
+ if (def?.type === "union" && Array.isArray(def.options)) return def.options;
721
+ }
722
+ function getObjectShape(schema) {
723
+ const def = getZodDef(schema);
724
+ if (def?.type === "object" && def.shape && typeof def.shape === "object") return def.shape;
725
+ }
726
+ /**
727
+ * Resolve the type annotation for a schema field.
728
+ * Returns a human-readable type string for the schema.
729
+ * If the schema is marked reactive(), prefixes with "$binding<...>".
730
+ */
731
+ function resolveTypeAnnotation(schema, reg) {
732
+ const isReactive = isReactiveSchema(schema);
733
+ const baseType = resolveBaseType(unwrap(schema), reg);
734
+ if (!baseType) return void 0;
735
+ return isReactive ? `$binding<${baseType}>` : baseType;
736
+ }
737
+ function resolveBaseType(inner, reg) {
738
+ const directId = getSchemaId(inner, reg);
739
+ if (directId) return directId;
740
+ const unionOpts = getUnionOptions(inner);
741
+ if (unionOpts) {
742
+ const names = unionOpts.map((o) => resolveTypeAnnotation(o, reg)).filter(Boolean);
743
+ if (names.length > 0) return names.join(" | ");
744
+ }
745
+ if (isArrayType(inner)) {
746
+ const arrayInner = getArrayInnerType(inner);
747
+ if (!arrayInner) return void 0;
748
+ const innerType = resolveTypeAnnotation(arrayInner, reg);
749
+ if (innerType) return getUnionOptions(unwrap(arrayInner)) !== void 0 ? `(${innerType})[]` : `${innerType}[]`;
750
+ return;
751
+ }
752
+ const zodType = getZodType(inner);
753
+ if (zodType === "string") return "string";
754
+ if (zodType === "number") return "number";
755
+ if (zodType === "boolean") return "boolean";
756
+ if (zodType === "any") return "any";
757
+ if (zodType === "record") {
758
+ const def = getZodDef(inner);
759
+ return `Record<${resolveTypeAnnotation(def?.keyType, reg) ?? "string"}, ${resolveTypeAnnotation(def?.valueType, reg) ?? "any"}>`;
760
+ }
761
+ const enumVals = getEnumValues(inner);
762
+ if (enumVals) return enumVals.map((v) => `"${v}"`).join(" | ");
763
+ if (zodType === "literal") {
764
+ const vals = getZodDef(inner)?.values;
765
+ if (Array.isArray(vals) && vals.length === 1) {
766
+ const v = vals[0];
767
+ return typeof v === "string" ? `"${v}"` : String(v);
768
+ }
769
+ }
770
+ const shape = getObjectShape(inner);
771
+ if (shape) return `{${Object.entries(shape).map(([name, fieldSchema]) => {
772
+ const opt = isOptionalType(fieldSchema) ? "?" : "";
773
+ const fieldType = resolveTypeAnnotation(fieldSchema, reg);
774
+ return fieldType ? `${name}${opt}: ${fieldType}` : `${name}${opt}`;
775
+ }).join(", ")}}`;
776
+ return "any";
777
+ }
778
+ function analyzeFields(shape, reg) {
779
+ return Object.entries(shape).map(([name, schema]) => ({
780
+ name,
781
+ isOptional: isOptionalType(schema),
782
+ isArray: isArrayType(schema),
783
+ typeAnnotation: resolveTypeAnnotation(schema, reg)
784
+ }));
785
+ }
786
+ function buildSignature(componentName, fields) {
787
+ return `${componentName}(${fields.map((f) => {
788
+ if (f.typeAnnotation) return f.isOptional ? `${f.name}?: ${f.typeAnnotation}` : `${f.name}: ${f.typeAnnotation}`;
789
+ if (f.isArray) return f.isOptional ? `[${f.name}]?` : `[${f.name}]`;
790
+ return f.isOptional ? `${f.name}?` : f.name;
791
+ }).join(", ")})`;
792
+ }
793
+ function buildComponentSpecs(components, reg) {
794
+ const specs = {};
795
+ for (const [name, def] of Object.entries(components)) specs[name] = {
796
+ signature: buildSignature(name, analyzeFields(def.props.shape, reg)),
797
+ description: def.description
798
+ };
799
+ return specs;
800
+ }
801
+ /**
802
+ * Create a component library from an array of defined components.
803
+ */
804
+ function createLibrary$1(input) {
805
+ const componentsRecord = {};
806
+ const reg = z$1.registry();
807
+ for (const comp of input.components) {
808
+ reg.add(comp.props, { id: comp.name });
809
+ componentsRecord[comp.name] = comp;
810
+ }
811
+ if (input.root && !componentsRecord[input.root]) {
812
+ const available = Object.keys(componentsRecord).join(", ");
813
+ throw new Error(`[createLibrary] Root component "${input.root}" was not found in components. Available components: ${available}`);
814
+ }
815
+ const library = {
816
+ components: componentsRecord,
817
+ componentGroups: input.componentGroups,
818
+ root: input.root,
819
+ id: input.id,
820
+ prompt(options) {
821
+ return generatePrompt({
822
+ root: input.root,
823
+ components: buildComponentSpecs(componentsRecord, reg),
824
+ componentGroups: input.componentGroups,
825
+ ...options
826
+ });
827
+ },
828
+ toSpec() {
829
+ return {
830
+ root: input.root,
831
+ components: buildComponentSpecs(componentsRecord, reg),
832
+ componentGroups: input.componentGroups,
833
+ schema: buildJSONSchema(),
834
+ ...input.id !== void 0 ? { id: input.id } : {}
835
+ };
836
+ },
837
+ toJSONSchema() {
838
+ return buildJSONSchema();
839
+ }
840
+ };
841
+ function buildJSONSchema() {
842
+ const combinedSchema = object(Object.fromEntries(Object.entries(componentsRecord).map(([k, v]) => [k, v.props])));
843
+ const schema = z$1.toJSONSchema(combinedSchema, { metadata: reg });
844
+ for (const [name, comp] of Object.entries(componentsRecord)) {
845
+ const def = schema.$defs?.[name];
846
+ if (def && comp.description) def.description = comp.description;
847
+ }
848
+ return schema;
849
+ }
850
+ return library;
851
+ }
852
+ function isElementNode(value) {
853
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
854
+ const node = value;
855
+ return node.type === "element" && typeof node.typeName === "string" && typeof node.props === "object" && node.props !== null && typeof node.partial === "boolean";
856
+ }
857
+ /**
858
+ * Built-in action types for host app events.
859
+ */
860
+ let BuiltinActionType = /* @__PURE__ */ function(BuiltinActionType) {
861
+ BuiltinActionType["ContinueConversation"] = "continue_conversation";
862
+ BuiltinActionType["OpenUrl"] = "open_url";
863
+ return BuiltinActionType;
864
+ }({});
865
+ /** Type guard for runtime expression nodes that survive parser lowering. */
866
+ function isRuntimeExpr(node) {
867
+ switch (node.k) {
868
+ case "StateRef":
869
+ case "RuntimeRef":
870
+ case "BinOp":
871
+ case "UnaryOp":
872
+ case "Ternary":
873
+ case "Member":
874
+ case "Index":
875
+ case "Assign": return true;
876
+ default: return false;
877
+ }
878
+ }
879
+ /** Valid AST discriminant values. */
880
+ const AST_KINDS = /* @__PURE__ */ new Set([
881
+ "Comp",
882
+ "Ref",
883
+ "StateRef",
884
+ "RuntimeRef",
885
+ "BinOp",
886
+ "UnaryOp",
887
+ "Ternary",
888
+ "Member",
889
+ "Index",
890
+ "Assign",
891
+ "Str",
892
+ "Num",
893
+ "Bool",
894
+ "Null",
895
+ "Arr",
896
+ "Obj",
897
+ "Ph"
898
+ ]);
899
+ /** Check if a value is an AST node (has a valid `k` discriminant field). */
900
+ function isASTNode(value) {
901
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
902
+ return AST_KINDS.has(value.k);
903
+ }
904
+ function walkAST(node, visit) {
905
+ const walk = (current) => {
906
+ visit(current);
907
+ switch (current.k) {
908
+ case "Comp":
909
+ current.args.forEach(walk);
910
+ Object.values(current.mappedProps ?? {}).forEach(walk);
911
+ break;
912
+ case "Arr":
913
+ current.els.forEach(walk);
914
+ break;
915
+ case "Obj":
916
+ current.entries.forEach(([, value]) => walk(value));
917
+ break;
918
+ case "BinOp":
919
+ walk(current.left);
920
+ walk(current.right);
921
+ break;
922
+ case "UnaryOp":
923
+ walk(current.operand);
924
+ break;
925
+ case "Ternary":
926
+ walk(current.cond);
927
+ walk(current.then);
928
+ walk(current.else);
929
+ break;
930
+ case "Member":
931
+ walk(current.obj);
932
+ break;
933
+ case "Index":
934
+ walk(current.obj);
935
+ walk(current.index);
936
+ break;
937
+ case "Assign":
938
+ walk(current.value);
939
+ break;
940
+ }
941
+ };
942
+ walk(node);
943
+ }
944
+ const PREC_TERNARY = 1;
945
+ const PREC_OR = 2;
946
+ const PREC_AND = 3;
947
+ const PREC_EQ = 4;
948
+ const PREC_CMP = 5;
949
+ const PREC_ADD = 6;
950
+ const PREC_MUL = 7;
951
+ const PREC_UNARY = 8;
952
+ const PREC_MEMBER = 9;
953
+ /**
954
+ * Parse a token array into an AST node using a Pratt (top-down operator
955
+ * precedence) parser.
956
+ */
957
+ function parseExpression(tokens) {
958
+ let pos = 0;
959
+ const cur = () => tokens[pos] ?? { t: 13 };
960
+ const adv = () => {
961
+ const tok = cur();
962
+ pos++;
963
+ return tok;
964
+ };
965
+ const eat = (kind) => {
966
+ if (cur().t === kind) pos++;
967
+ };
968
+ function getInfixPrec(tok) {
969
+ switch (tok.t) {
970
+ case 34: return PREC_TERNARY;
971
+ case 32: return PREC_OR;
972
+ case 31: return PREC_AND;
973
+ case 25:
974
+ case 26: return PREC_EQ;
975
+ case 27:
976
+ case 28:
977
+ case 29:
978
+ case 30: return PREC_CMP;
979
+ case 20:
980
+ case 21: return PREC_ADD;
981
+ case 22:
982
+ case 23:
983
+ case 24: return PREC_MUL;
984
+ case 19:
985
+ case 3: return PREC_MEMBER;
986
+ default: return 0;
987
+ }
988
+ }
989
+ function parseExpr(minPrec = 0) {
990
+ let left = parsePrefix();
991
+ while (getInfixPrec(cur()) > minPrec) left = parseInfix(left);
992
+ return left;
993
+ }
994
+ function parsePrefix() {
995
+ const tok = cur();
996
+ if (tok.t === 14) {
997
+ adv();
998
+ return {
999
+ k: "Str",
1000
+ v: tok.v
1001
+ };
1002
+ }
1003
+ if (tok.t === 15) {
1004
+ adv();
1005
+ return {
1006
+ k: "Num",
1007
+ v: tok.v
1008
+ };
1009
+ }
1010
+ if (tok.t === 10) {
1011
+ adv();
1012
+ return {
1013
+ k: "Bool",
1014
+ v: true
1015
+ };
1016
+ }
1017
+ if (tok.t === 11) {
1018
+ adv();
1019
+ return {
1020
+ k: "Bool",
1021
+ v: false
1022
+ };
1023
+ }
1024
+ if (tok.t === 12) {
1025
+ adv();
1026
+ return { k: "Null" };
1027
+ }
1028
+ if (tok.t === 3) return parseArr();
1029
+ if (tok.t === 5) return parseObj();
1030
+ if (tok.t === 18) {
1031
+ const name = tok.v;
1032
+ adv();
1033
+ if (cur().t === 9) {
1034
+ adv();
1035
+ return {
1036
+ k: "Assign",
1037
+ target: name,
1038
+ value: parseExpr(0)
1039
+ };
1040
+ }
1041
+ return {
1042
+ k: "StateRef",
1043
+ n: name
1044
+ };
1045
+ }
1046
+ if (tok.t === 17) {
1047
+ const name = tok.v;
1048
+ if (tokens[pos + 1]?.t === 1 && (!isBuiltin(name) || name === "Action")) return parseComp();
1049
+ adv();
1050
+ return {
1051
+ k: "Ref",
1052
+ n: name
1053
+ };
1054
+ }
1055
+ if (tok.t === 35) {
1056
+ if (tokens[pos + 1]?.t === 1) return parseComp();
1057
+ adv();
1058
+ return {
1059
+ k: "Ref",
1060
+ n: tok.v
1061
+ };
1062
+ }
1063
+ if (tok.t === 16) {
1064
+ adv();
1065
+ return {
1066
+ k: "Ref",
1067
+ n: tok.v
1068
+ };
1069
+ }
1070
+ if (tok.t === 33) {
1071
+ adv();
1072
+ return {
1073
+ k: "UnaryOp",
1074
+ op: "!",
1075
+ operand: parseExpr(PREC_UNARY)
1076
+ };
1077
+ }
1078
+ if (tok.t === 21) {
1079
+ adv();
1080
+ return {
1081
+ k: "UnaryOp",
1082
+ op: "-",
1083
+ operand: parseExpr(PREC_UNARY)
1084
+ };
1085
+ }
1086
+ if (tok.t === 1) {
1087
+ adv();
1088
+ const inner = parseExpr(0);
1089
+ eat(2);
1090
+ return inner;
1091
+ }
1092
+ adv();
1093
+ return { k: "Null" };
1094
+ }
1095
+ function parseInfix(left) {
1096
+ const tok = cur();
1097
+ if (tok.t === 20) {
1098
+ adv();
1099
+ return {
1100
+ k: "BinOp",
1101
+ op: "+",
1102
+ left,
1103
+ right: parseExpr(PREC_ADD)
1104
+ };
1105
+ }
1106
+ if (tok.t === 21) {
1107
+ adv();
1108
+ return {
1109
+ k: "BinOp",
1110
+ op: "-",
1111
+ left,
1112
+ right: parseExpr(PREC_ADD)
1113
+ };
1114
+ }
1115
+ if (tok.t === 22) {
1116
+ adv();
1117
+ return {
1118
+ k: "BinOp",
1119
+ op: "*",
1120
+ left,
1121
+ right: parseExpr(PREC_MUL)
1122
+ };
1123
+ }
1124
+ if (tok.t === 23) {
1125
+ adv();
1126
+ return {
1127
+ k: "BinOp",
1128
+ op: "/",
1129
+ left,
1130
+ right: parseExpr(PREC_MUL)
1131
+ };
1132
+ }
1133
+ if (tok.t === 24) {
1134
+ adv();
1135
+ return {
1136
+ k: "BinOp",
1137
+ op: "%",
1138
+ left,
1139
+ right: parseExpr(PREC_MUL)
1140
+ };
1141
+ }
1142
+ if (tok.t === 25) {
1143
+ adv();
1144
+ return {
1145
+ k: "BinOp",
1146
+ op: "==",
1147
+ left,
1148
+ right: parseExpr(PREC_EQ)
1149
+ };
1150
+ }
1151
+ if (tok.t === 26) {
1152
+ adv();
1153
+ return {
1154
+ k: "BinOp",
1155
+ op: "!=",
1156
+ left,
1157
+ right: parseExpr(PREC_EQ)
1158
+ };
1159
+ }
1160
+ if (tok.t === 27) {
1161
+ adv();
1162
+ return {
1163
+ k: "BinOp",
1164
+ op: ">",
1165
+ left,
1166
+ right: parseExpr(PREC_CMP)
1167
+ };
1168
+ }
1169
+ if (tok.t === 28) {
1170
+ adv();
1171
+ return {
1172
+ k: "BinOp",
1173
+ op: "<",
1174
+ left,
1175
+ right: parseExpr(PREC_CMP)
1176
+ };
1177
+ }
1178
+ if (tok.t === 29) {
1179
+ adv();
1180
+ return {
1181
+ k: "BinOp",
1182
+ op: ">=",
1183
+ left,
1184
+ right: parseExpr(PREC_CMP)
1185
+ };
1186
+ }
1187
+ if (tok.t === 30) {
1188
+ adv();
1189
+ return {
1190
+ k: "BinOp",
1191
+ op: "<=",
1192
+ left,
1193
+ right: parseExpr(PREC_CMP)
1194
+ };
1195
+ }
1196
+ if (tok.t === 31) {
1197
+ adv();
1198
+ return {
1199
+ k: "BinOp",
1200
+ op: "&&",
1201
+ left,
1202
+ right: parseExpr(PREC_AND)
1203
+ };
1204
+ }
1205
+ if (tok.t === 32) {
1206
+ adv();
1207
+ return {
1208
+ k: "BinOp",
1209
+ op: "||",
1210
+ left,
1211
+ right: parseExpr(PREC_OR)
1212
+ };
1213
+ }
1214
+ if (tok.t === 34) {
1215
+ adv();
1216
+ const then = parseExpr(0);
1217
+ eat(8);
1218
+ return {
1219
+ k: "Ternary",
1220
+ cond: left,
1221
+ then,
1222
+ else: parseExpr(0)
1223
+ };
1224
+ }
1225
+ if (tok.t === 19) {
1226
+ adv();
1227
+ const fieldTok = cur();
1228
+ return {
1229
+ k: "Member",
1230
+ obj: left,
1231
+ field: fieldTok.t === 16 || fieldTok.t === 17 || fieldTok.t === 14 || fieldTok.t === 15 ? (adv(), String(fieldTok.v)) : fieldTok.t === 18 ? (adv(), fieldTok.v.replace(/^\$/, "")) : (adv(), "?")
1232
+ };
1233
+ }
1234
+ if (tok.t === 3) {
1235
+ adv();
1236
+ const index = parseExpr(0);
1237
+ eat(4);
1238
+ return {
1239
+ k: "Index",
1240
+ obj: left,
1241
+ index
1242
+ };
1243
+ }
1244
+ return left;
1245
+ }
1246
+ /** Parse `TypeName(arg1, arg2, ...)` */
1247
+ function parseComp() {
1248
+ const name = cur().v;
1249
+ adv();
1250
+ eat(1);
1251
+ const args = [];
1252
+ while (cur().t !== 2 && cur().t !== 13) {
1253
+ args.push(parseExpr(0));
1254
+ if (cur().t === 7) adv();
1255
+ }
1256
+ eat(2);
1257
+ return {
1258
+ k: "Comp",
1259
+ name,
1260
+ args
1261
+ };
1262
+ }
1263
+ /** Parse `[elem1, elem2, ...]` */
1264
+ function parseArr() {
1265
+ adv();
1266
+ const els = [];
1267
+ while (cur().t !== 4 && cur().t !== 13) {
1268
+ els.push(parseExpr(0));
1269
+ if (cur().t === 7) adv();
1270
+ }
1271
+ eat(4);
1272
+ return {
1273
+ k: "Arr",
1274
+ els
1275
+ };
1276
+ }
1277
+ /** Parse `{ key: value, ... }` */
1278
+ function parseObj() {
1279
+ adv();
1280
+ const entries = [];
1281
+ while (cur().t !== 6 && cur().t !== 13) {
1282
+ const kt = cur();
1283
+ const key = kt.t === 16 || kt.t === 14 || kt.t === 17 || kt.t === 15 ? (adv(), String(kt.v)) : kt.t === 18 ? (adv(), kt.v.replace(/^\$/, "")) : (adv(), "?");
1284
+ eat(8);
1285
+ entries.push([key, parseExpr(0)]);
1286
+ if (cur().t === 7) adv();
1287
+ }
1288
+ eat(6);
1289
+ return {
1290
+ k: "Obj",
1291
+ entries
1292
+ };
1293
+ }
1294
+ return parseExpr(0);
1295
+ }
1296
+ /**
1297
+ * Tokenize an openui-lang source string into a flat token array.
1298
+ *
1299
+ * Handles all token types: identifiers, literals, operators,
1300
+ * state variables ($name), dot access, ternary.
1301
+ */
1302
+ function tokenize(src) {
1303
+ const tokens = [];
1304
+ let i = 0;
1305
+ const n = src.length;
1306
+ while (i < n) {
1307
+ while (i < n && (src[i] === " " || src[i] === " " || src[i] === "\r")) i++;
1308
+ if (i >= n) break;
1309
+ const c = src[i];
1310
+ if (c === "\n") {
1311
+ tokens.push({ t: 0 });
1312
+ i++;
1313
+ continue;
1314
+ }
1315
+ if (c === "(") {
1316
+ tokens.push({ t: 1 });
1317
+ i++;
1318
+ continue;
1319
+ }
1320
+ if (c === ")") {
1321
+ tokens.push({ t: 2 });
1322
+ i++;
1323
+ continue;
1324
+ }
1325
+ if (c === "[") {
1326
+ tokens.push({ t: 3 });
1327
+ i++;
1328
+ continue;
1329
+ }
1330
+ if (c === "]") {
1331
+ tokens.push({ t: 4 });
1332
+ i++;
1333
+ continue;
1334
+ }
1335
+ if (c === "{") {
1336
+ tokens.push({ t: 5 });
1337
+ i++;
1338
+ continue;
1339
+ }
1340
+ if (c === "}") {
1341
+ tokens.push({ t: 6 });
1342
+ i++;
1343
+ continue;
1344
+ }
1345
+ if (c === ",") {
1346
+ tokens.push({ t: 7 });
1347
+ i++;
1348
+ continue;
1349
+ }
1350
+ if (c === ":") {
1351
+ tokens.push({ t: 8 });
1352
+ i++;
1353
+ continue;
1354
+ }
1355
+ if (c === "=") {
1356
+ if (i + 1 < n && src[i + 1] === "=") {
1357
+ tokens.push({ t: 25 });
1358
+ i += 2;
1359
+ } else {
1360
+ tokens.push({ t: 9 });
1361
+ i++;
1362
+ }
1363
+ continue;
1364
+ }
1365
+ if (c === "!") {
1366
+ if (i + 1 < n && src[i + 1] === "=") {
1367
+ tokens.push({ t: 26 });
1368
+ i += 2;
1369
+ } else {
1370
+ tokens.push({ t: 33 });
1371
+ i++;
1372
+ }
1373
+ continue;
1374
+ }
1375
+ if (c === ">") {
1376
+ if (i + 1 < n && src[i + 1] === "=") {
1377
+ tokens.push({ t: 29 });
1378
+ i += 2;
1379
+ } else {
1380
+ tokens.push({ t: 27 });
1381
+ i++;
1382
+ }
1383
+ continue;
1384
+ }
1385
+ if (c === "<") {
1386
+ if (i + 1 < n && src[i + 1] === "=") {
1387
+ tokens.push({ t: 30 });
1388
+ i += 2;
1389
+ } else {
1390
+ tokens.push({ t: 28 });
1391
+ i++;
1392
+ }
1393
+ continue;
1394
+ }
1395
+ if (c === "&") {
1396
+ if (i + 1 < n && src[i + 1] === "&") {
1397
+ tokens.push({ t: 31 });
1398
+ i += 2;
1399
+ } else {
1400
+ tokens.push({ t: 31 });
1401
+ i++;
1402
+ }
1403
+ continue;
1404
+ }
1405
+ if (c === "|") {
1406
+ if (i + 1 < n && src[i + 1] === "|") {
1407
+ tokens.push({ t: 32 });
1408
+ i += 2;
1409
+ } else {
1410
+ tokens.push({ t: 32 });
1411
+ i++;
1412
+ }
1413
+ continue;
1414
+ }
1415
+ if (c === ".") {
1416
+ tokens.push({ t: 19 });
1417
+ i++;
1418
+ continue;
1419
+ }
1420
+ if (c === "?") {
1421
+ tokens.push({ t: 34 });
1422
+ i++;
1423
+ continue;
1424
+ }
1425
+ if (c === "+") {
1426
+ tokens.push({ t: 20 });
1427
+ i++;
1428
+ continue;
1429
+ }
1430
+ if (c === "*") {
1431
+ tokens.push({ t: 22 });
1432
+ i++;
1433
+ continue;
1434
+ }
1435
+ if (c === "/") {
1436
+ tokens.push({ t: 23 });
1437
+ i++;
1438
+ continue;
1439
+ }
1440
+ if (c === "%") {
1441
+ tokens.push({ t: 24 });
1442
+ i++;
1443
+ continue;
1444
+ }
1445
+ if (c === "\"") {
1446
+ const start = i;
1447
+ i++;
1448
+ let isClosed = false;
1449
+ while (i < n) if (src[i] === "\\") i += 2;
1450
+ else if (src[i] === "\"") {
1451
+ i++;
1452
+ isClosed = true;
1453
+ break;
1454
+ } else i++;
1455
+ const rawString = src.slice(start, i);
1456
+ try {
1457
+ const validJsonString = isClosed ? rawString : rawString + "\"";
1458
+ tokens.push({
1459
+ t: 14,
1460
+ v: JSON.parse(validJsonString)
1461
+ });
1462
+ } catch {
1463
+ const stripped = rawString.replace(/^"|"$/g, "");
1464
+ tokens.push({
1465
+ t: 14,
1466
+ v: stripped
1467
+ });
1468
+ }
1469
+ continue;
1470
+ }
1471
+ if (c === "'") {
1472
+ i++;
1473
+ let result = "";
1474
+ while (i < n) if (src[i] === "\\") {
1475
+ i++;
1476
+ if (i < n) {
1477
+ const esc = src[i];
1478
+ if (esc === "'") result += "'";
1479
+ else if (esc === "\\") result += "\\";
1480
+ else if (esc === "n") result += "\n";
1481
+ else if (esc === "t") result += " ";
1482
+ else result += esc;
1483
+ i++;
1484
+ }
1485
+ } else if (src[i] === "'") {
1486
+ i++;
1487
+ break;
1488
+ } else {
1489
+ result += src[i];
1490
+ i++;
1491
+ }
1492
+ tokens.push({
1493
+ t: 14,
1494
+ v: result
1495
+ });
1496
+ continue;
1497
+ }
1498
+ if (c === "-") {
1499
+ const prev = tokens.length > 0 ? tokens[tokens.length - 1] : null;
1500
+ if (!(prev != null && (prev.t === 15 || prev.t === 14 || prev.t === 16 || prev.t === 17 || prev.t === 2 || prev.t === 4 || prev.t === 10 || prev.t === 11 || prev.t === 12 || prev.t === 18 || prev.t === 35)) && i + 1 < n && src[i + 1] >= "0" && src[i + 1] <= "9") {} else {
1501
+ tokens.push({ t: 21 });
1502
+ i++;
1503
+ continue;
1504
+ }
1505
+ }
1506
+ const isDigit = c >= "0" && c <= "9";
1507
+ const isNegDigit = c === "-" && i + 1 < n && src[i + 1] >= "0" && src[i + 1] <= "9";
1508
+ if (isDigit || isNegDigit) {
1509
+ const start = i;
1510
+ if (src[i] === "-") i++;
1511
+ while (i < n && src[i] >= "0" && src[i] <= "9") i++;
1512
+ if (i < n && src[i] === "." && i + 1 < n && src[i + 1] >= "0" && src[i + 1] <= "9") {
1513
+ i++;
1514
+ while (i < n && src[i] >= "0" && src[i] <= "9") i++;
1515
+ }
1516
+ if (i < n && (src[i] === "e" || src[i] === "E")) {
1517
+ i++;
1518
+ if (i < n && (src[i] === "+" || src[i] === "-")) i++;
1519
+ while (i < n && src[i] >= "0" && src[i] <= "9") i++;
1520
+ }
1521
+ tokens.push({
1522
+ t: 15,
1523
+ v: +src.slice(start, i)
1524
+ });
1525
+ continue;
1526
+ }
1527
+ if (c === "$" && i + 1 < n && (src[i + 1] >= "a" && src[i + 1] <= "z" || src[i + 1] >= "A" && src[i + 1] <= "Z" || src[i + 1] === "_")) {
1528
+ const start = i;
1529
+ i++;
1530
+ while (i < n && (src[i] >= "a" && src[i] <= "z" || src[i] >= "A" && src[i] <= "Z" || src[i] >= "0" && src[i] <= "9" || src[i] === "_")) i++;
1531
+ tokens.push({
1532
+ t: 18,
1533
+ v: src.slice(start, i)
1534
+ });
1535
+ continue;
1536
+ }
1537
+ if (c >= "a" && c <= "z" || c >= "A" && c <= "Z" || c === "_") {
1538
+ const start = i;
1539
+ while (i < n && (src[i] >= "a" && src[i] <= "z" || src[i] >= "A" && src[i] <= "Z" || src[i] >= "0" && src[i] <= "9" || src[i] === "_")) i++;
1540
+ const word = src.slice(start, i);
1541
+ if (word === "true") {
1542
+ tokens.push({ t: 10 });
1543
+ continue;
1544
+ }
1545
+ if (word === "false") {
1546
+ tokens.push({ t: 11 });
1547
+ continue;
1548
+ }
1549
+ if (word === "null") {
1550
+ tokens.push({ t: 12 });
1551
+ continue;
1552
+ }
1553
+ const kind = c >= "A" && c <= "Z" ? 17 : 16;
1554
+ tokens.push({
1555
+ t: kind,
1556
+ v: word
1557
+ });
1558
+ continue;
1559
+ }
1560
+ if (c === "@" && i + 1 < n && (src[i + 1] >= "a" && src[i + 1] <= "z" || src[i + 1] >= "A" && src[i + 1] <= "Z" || src[i + 1] === "_")) {
1561
+ i++;
1562
+ const start = i;
1563
+ while (i < n && (src[i] >= "a" && src[i] <= "z" || src[i] >= "A" && src[i] <= "Z" || src[i] >= "0" && src[i] <= "9" || src[i] === "_")) i++;
1564
+ tokens.push({
1565
+ t: 35,
1566
+ v: src.slice(start, i)
1567
+ });
1568
+ continue;
1569
+ }
1570
+ i++;
1571
+ }
1572
+ tokens.push({ t: 13 });
1573
+ return tokens;
1574
+ }
1575
+ /**
1576
+ * Recursively check if a prop value contains any AST nodes that need runtime
1577
+ * evaluation. Walks into arrays, ElementNode children, and plain objects.
1578
+ */
1579
+ function containsDynamicValue(v) {
1580
+ if (v == null || typeof v !== "object") return false;
1581
+ if (isASTNode(v)) return true;
1582
+ if (Array.isArray(v)) return v.some(containsDynamicValue);
1583
+ if (isElementNode(v)) return Object.values(v.props).some(containsDynamicValue);
1584
+ return Object.values(v).some(containsDynamicValue);
1585
+ }
1586
+ /**
1587
+ * Resolve a Ref node: inline from symbol table, detect cycles, emit RuntimeRef
1588
+ * for Query/Mutation declarations. Shared by materializeValue and materializeExpr.
1589
+ */
1590
+ function resolveRef(name, ctx, mode) {
1591
+ if (ctx.visited.has(name)) {
1592
+ ctx.unres.push(name);
1593
+ return mode === "expr" ? {
1594
+ k: "Ph",
1595
+ n: name
1596
+ } : null;
1597
+ }
1598
+ if (!ctx.syms.has(name)) {
1599
+ ctx.unres.push(name);
1600
+ return mode === "expr" ? {
1601
+ k: "Ph",
1602
+ n: name
1603
+ } : null;
1604
+ }
1605
+ const target = ctx.syms.get(name);
1606
+ ctx.unreached?.delete(name);
1607
+ if (target.k === "Comp" && isReservedCall(target.name)) return {
1608
+ k: "RuntimeRef",
1609
+ n: name,
1610
+ refType: target.name === RESERVED_CALLS.Mutation ? "mutation" : "query"
1611
+ };
1612
+ ctx.visited.add(name);
1613
+ const prevStatementId = ctx.currentStatementId;
1614
+ ctx.currentStatementId = name;
1615
+ try {
1616
+ const result = mode === "value" ? materializeValue(target, ctx) : materializeExpr(target, ctx);
1617
+ if (mode === "value" && isElementNode(result)) result.statementId = name;
1618
+ return result;
1619
+ } finally {
1620
+ ctx.currentStatementId = prevStatementId;
1621
+ ctx.visited.delete(name);
1622
+ }
1623
+ }
1624
+ /**
1625
+ * If node is a lazy builtin like Each(arr, varName, template), temporarily
1626
+ * scope the iterator variable during materialization so template refs resolve.
1627
+ * Returns the materialized Comp node, or null if not a lazy builtin.
1628
+ */
1629
+ function materializeLazyBuiltin(node, ctx, scopedRefs) {
1630
+ if (!LAZY_BUILTINS.has(node.name) || node.args.length < 3) return null;
1631
+ const varArg = node.args[1];
1632
+ const varName = varArg.k === "Ref" ? varArg.n : varArg.k === "Str" ? varArg.v : null;
1633
+ if (!varName) return null;
1634
+ const nextScopedRefs = new Set(scopedRefs);
1635
+ nextScopedRefs.add(varName);
1636
+ const recursedArgs = node.args.map((a, i) => i === 1 ? a : materializeExprInternal(a, ctx, nextScopedRefs));
1637
+ return {
1638
+ ...node,
1639
+ args: recursedArgs
1640
+ };
1641
+ }
1642
+ function materializeExprInternal(node, ctx, scopedRefs) {
1643
+ switch (node.k) {
1644
+ case "Ref": return scopedRefs.has(node.n) ? node : resolveRef(node.n, ctx, "expr");
1645
+ case "Ph": return node;
1646
+ case "Comp": {
1647
+ const lazy = materializeLazyBuiltin(node, ctx, scopedRefs);
1648
+ if (lazy) return lazy;
1649
+ const recursedArgs = node.args.map((a) => materializeExprInternal(a, ctx, scopedRefs));
1650
+ if (isBuiltin(node.name) || isReservedCall(node.name)) return {
1651
+ ...node,
1652
+ args: recursedArgs
1653
+ };
1654
+ const def = ctx.cat?.get(node.name);
1655
+ if (def) {
1656
+ const mappedProps = {};
1657
+ for (let i = 0; i < def.params.length && i < recursedArgs.length; i++) mappedProps[def.params[i].name] = recursedArgs[i];
1658
+ return {
1659
+ ...node,
1660
+ args: recursedArgs,
1661
+ mappedProps
1662
+ };
1663
+ }
1664
+ ctx.errors.push({
1665
+ code: "unknown-component",
1666
+ component: node.name,
1667
+ path: "",
1668
+ message: `Unknown component "${node.name}" — not found in catalog or builtins`,
1669
+ statementId: ctx.currentStatementId
1670
+ });
1671
+ return {
1672
+ ...node,
1673
+ args: recursedArgs
1674
+ };
1675
+ }
1676
+ case "Arr": return {
1677
+ ...node,
1678
+ els: node.els.map((e) => materializeExprInternal(e, ctx, scopedRefs))
1679
+ };
1680
+ case "Obj": return {
1681
+ ...node,
1682
+ entries: node.entries.map(([k, v]) => [k, materializeExprInternal(v, ctx, scopedRefs)])
1683
+ };
1684
+ case "BinOp": return {
1685
+ ...node,
1686
+ left: materializeExprInternal(node.left, ctx, scopedRefs),
1687
+ right: materializeExprInternal(node.right, ctx, scopedRefs)
1688
+ };
1689
+ case "UnaryOp": return {
1690
+ ...node,
1691
+ operand: materializeExprInternal(node.operand, ctx, scopedRefs)
1692
+ };
1693
+ case "Ternary": return {
1694
+ ...node,
1695
+ cond: materializeExprInternal(node.cond, ctx, scopedRefs),
1696
+ then: materializeExprInternal(node.then, ctx, scopedRefs),
1697
+ else: materializeExprInternal(node.else, ctx, scopedRefs)
1698
+ };
1699
+ case "Member": return {
1700
+ ...node,
1701
+ obj: materializeExprInternal(node.obj, ctx, scopedRefs)
1702
+ };
1703
+ case "Index": return {
1704
+ ...node,
1705
+ obj: materializeExprInternal(node.obj, ctx, scopedRefs),
1706
+ index: materializeExprInternal(node.index, ctx, scopedRefs)
1707
+ };
1708
+ case "Assign": return {
1709
+ ...node,
1710
+ value: materializeExprInternal(node.value, ctx, scopedRefs)
1711
+ };
1712
+ default: return node;
1713
+ }
1714
+ }
1715
+ /**
1716
+ * Normalize an AST node for use inside runtime expressions.
1717
+ * Resolves Refs, adds mappedProps to catalog Comp nodes.
1718
+ * Returns ASTNode — structure preserved for runtime evaluation by the evaluator.
1719
+ */
1720
+ function materializeExpr(node, ctx) {
1721
+ return materializeExprInternal(node, ctx, /* @__PURE__ */ new Set());
1722
+ }
1723
+ /**
1724
+ * Schema-aware materialization: resolves refs, normalizes catalog component args
1725
+ * to named props, validates required props, applies defaults, converts literals
1726
+ * to plain values, and preserves runtime expressions as AST nodes — all in a
1727
+ * single recursive traversal.
1728
+ *
1729
+ * Returns:
1730
+ * - ElementNode for catalog/unknown components
1731
+ * - ASTNode for builtins and runtime expression nodes
1732
+ * - Plain values for literals, arrays, objects
1733
+ * - null for placeholders
1734
+ */
1735
+ function materializeValue(node, ctx) {
1736
+ switch (node.k) {
1737
+ case "Ref": return resolveRef(node.n, ctx, "value");
1738
+ case "Str": return node.v;
1739
+ case "Num": return node.v;
1740
+ case "Bool": return node.v;
1741
+ case "Null": return null;
1742
+ case "Ph": return null;
1743
+ case "Arr": {
1744
+ const items = [];
1745
+ for (const e of node.els) {
1746
+ if (e.k === "Ph") continue;
1747
+ const value = materializeValue(e, ctx);
1748
+ if (value === null && (e.k === "Comp" || e.k === "Ref")) continue;
1749
+ items.push(value);
1750
+ }
1751
+ return items;
1752
+ }
1753
+ case "Obj": {
1754
+ const o = {};
1755
+ for (const [k, v] of node.entries) o[k] = materializeValue(v, ctx);
1756
+ return o;
1757
+ }
1758
+ case "Comp": {
1759
+ const { name, args } = node;
1760
+ if (isBuiltin(name)) {
1761
+ const lazy = materializeLazyBuiltin(node, ctx, /* @__PURE__ */ new Set());
1762
+ if (lazy) return lazy;
1763
+ return {
1764
+ ...node,
1765
+ args: args.map((a) => materializeExpr(a, ctx))
1766
+ };
1767
+ }
1768
+ if (isReservedCall(name)) {
1769
+ ctx.errors.push({
1770
+ code: "inline-reserved",
1771
+ component: name,
1772
+ path: "",
1773
+ message: `${name}() must be declared as a top-level statement, not used inline as a value`,
1774
+ statementId: ctx.currentStatementId
1775
+ });
1776
+ return null;
1777
+ }
1778
+ const def = ctx.cat?.get(name);
1779
+ const props = {};
1780
+ if (def) {
1781
+ for (let i = 0; i < def.params.length && i < args.length; i++) props[def.params[i].name] = materializeValue(args[i], ctx);
1782
+ if (args.length > def.params.length) {
1783
+ const excessCount = args.length - def.params.length;
1784
+ ctx.errors.push({
1785
+ code: "excess-args",
1786
+ component: name,
1787
+ path: "",
1788
+ message: `${name} takes ${def.params.length} arg(s), got ${args.length} (${excessCount} excess dropped)`,
1789
+ statementId: ctx.currentStatementId
1790
+ });
1791
+ }
1792
+ const missingRequired = def.params.filter((p) => p.required && (!(p.name in props) || props[p.name] === null));
1793
+ if (missingRequired.length) {
1794
+ const stillInvalid = missingRequired.filter((p) => {
1795
+ if (p.defaultValue !== void 0) {
1796
+ props[p.name] = p.defaultValue;
1797
+ return false;
1798
+ }
1799
+ return true;
1800
+ });
1801
+ if (stillInvalid.length) {
1802
+ for (const p of stillInvalid) {
1803
+ const isNull = p.name in props;
1804
+ ctx.errors.push({
1805
+ code: isNull ? "null-required" : "missing-required",
1806
+ component: name,
1807
+ path: `/${p.name}`,
1808
+ message: isNull ? `required field "${p.name}" cannot be null` : `missing required field "${p.name}"`,
1809
+ statementId: ctx.currentStatementId
1810
+ });
1811
+ }
1812
+ return null;
1813
+ }
1814
+ }
1815
+ } else if (!isBuiltin(name) && !isReservedCall(name)) {
1816
+ ctx.errors.push({
1817
+ code: "unknown-component",
1818
+ component: name,
1819
+ path: "",
1820
+ message: `Unknown component "${name}" — not found in catalog or builtins`,
1821
+ statementId: ctx.currentStatementId
1822
+ });
1823
+ return null;
1824
+ }
1825
+ const hasDynamicProps = Object.values(props).some((v) => containsDynamicValue(v));
1826
+ return {
1827
+ type: "element",
1828
+ typeName: name,
1829
+ props,
1830
+ partial: ctx.partial,
1831
+ hasDynamicProps
1832
+ };
1833
+ }
1834
+ default:
1835
+ if (isRuntimeExpr(node)) return materializeExpr(node, ctx);
1836
+ return node;
1837
+ }
1838
+ }
1839
+ /**
1840
+ * Auto-close unclosed strings and brackets so that partial/streaming input
1841
+ * can be parsed without syntax errors.
1842
+ */
1843
+ function autoClose(input) {
1844
+ const stack = [];
1845
+ let inStr = false;
1846
+ let esc = false;
1847
+ for (let i = 0; i < input.length; i++) {
1848
+ const c = input[i];
1849
+ if (esc) {
1850
+ esc = false;
1851
+ continue;
1852
+ }
1853
+ if (c === "\\" && inStr) {
1854
+ esc = true;
1855
+ continue;
1856
+ }
1857
+ if (inStr) {
1858
+ if (c === inStr) inStr = false;
1859
+ continue;
1860
+ }
1861
+ if (c === "\"" || c === "'") {
1862
+ inStr = c;
1863
+ continue;
1864
+ }
1865
+ if (c === "(" || c === "[" || c === "{") stack.push(c);
1866
+ else if (c === ")" && stack[stack.length - 1] === "(") stack.pop();
1867
+ else if (c === "]" && stack[stack.length - 1] === "[") stack.pop();
1868
+ else if (c === "}" && stack[stack.length - 1] === "{") stack.pop();
1869
+ }
1870
+ if (!(!!inStr || stack.length > 0)) return {
1871
+ text: input,
1872
+ wasIncomplete: false
1873
+ };
1874
+ let out = input;
1875
+ if (inStr) {
1876
+ if (esc) out += "\\";
1877
+ out += inStr;
1878
+ }
1879
+ for (let j = stack.length - 1; j >= 0; j--) out += stack[j] === "(" ? ")" : stack[j] === "[" ? "]" : "}";
1880
+ return {
1881
+ text: out,
1882
+ wasIncomplete: true
1883
+ };
1884
+ }
1885
+ /**
1886
+ * Splits the flat token stream into individual statements.
1887
+ *
1888
+ * Each statement has the form `identifier = expression`. Statements are
1889
+ * separated by newlines at depth 0 (newlines inside brackets are ignored).
1890
+ *
1891
+ * Accepts `Ident`, `Type`, and `StateVar` as statement identifiers.
1892
+ * For StateVar, the id is the full token value including $ (e.g., "$count").
1893
+ *
1894
+ * Invalid lines (no `=`, or no identifier) are silently skipped.
1895
+ */
1896
+ function split(tokens) {
1897
+ const stmts = [];
1898
+ let pos = 0;
1899
+ while (pos < tokens.length) {
1900
+ while (pos < tokens.length && tokens[pos].t === 0) pos++;
1901
+ if (pos >= tokens.length || tokens[pos].t === 13) break;
1902
+ const tok = tokens[pos];
1903
+ if (tok.t !== 16 && tok.t !== 17 && tok.t !== 18) {
1904
+ while (pos < tokens.length && tokens[pos].t !== 0 && tokens[pos].t !== 13) pos++;
1905
+ continue;
1906
+ }
1907
+ const id = tok.v;
1908
+ const idTokenType = tok.t;
1909
+ pos++;
1910
+ if (pos >= tokens.length || tokens[pos].t !== 9) {
1911
+ while (pos < tokens.length && tokens[pos].t !== 0 && tokens[pos].t !== 13) pos++;
1912
+ continue;
1913
+ }
1914
+ pos++;
1915
+ const expr = [];
1916
+ let depth = 0;
1917
+ let ternaryDepth = 0;
1918
+ while (pos < tokens.length && tokens[pos].t !== 13) {
1919
+ const tt = tokens[pos].t;
1920
+ if (tt === 0 && depth <= 0 && ternaryDepth <= 0) {
1921
+ let peek = pos + 1;
1922
+ while (peek < tokens.length && tokens[peek].t === 0) peek++;
1923
+ const nextT = peek < tokens.length ? tokens[peek].t : 13;
1924
+ if (nextT === 34 || nextT === 8 && ternaryDepth > 0) {
1925
+ pos++;
1926
+ continue;
1927
+ }
1928
+ break;
1929
+ }
1930
+ if (tt === 0) {
1931
+ pos++;
1932
+ continue;
1933
+ }
1934
+ if (tt === 1 || tt === 3 || tt === 5) depth++;
1935
+ else if ((tt === 2 || tt === 4 || tt === 6) && depth > 0) depth--;
1936
+ else if (tt === 34 && depth === 0) ternaryDepth++;
1937
+ else if (tt === 8 && depth === 0 && ternaryDepth > 0) ternaryDepth--;
1938
+ expr.push(tokens[pos++]);
1939
+ }
1940
+ if (expr.length) stmts.push({
1941
+ id,
1942
+ idTokenType,
1943
+ tokens: expr
1944
+ });
1945
+ }
1946
+ return stmts;
1947
+ }
1948
+ function emptyResult(incomplete = true) {
1949
+ return {
1950
+ root: null,
1951
+ meta: {
1952
+ incomplete,
1953
+ unresolved: [],
1954
+ orphaned: [],
1955
+ statementCount: 0,
1956
+ errors: []
1957
+ },
1958
+ stateDeclarations: {},
1959
+ queryStatements: [],
1960
+ mutationStatements: []
1961
+ };
1962
+ }
1963
+ /**
1964
+ * Walk an AST node to collect all StateRef ($variable) names referenced
1965
+ * within. Used at parse time to pre-compute per-query state dependencies.
1966
+ */
1967
+ function collectQueryDeps(node) {
1968
+ if (!isASTNode(node)) return [];
1969
+ const refs = /* @__PURE__ */ new Set();
1970
+ walkAST(node, (current) => {
1971
+ if (current.k === "StateRef") refs.add(current.n);
1972
+ });
1973
+ return [...refs];
1974
+ }
1975
+ /**
1976
+ * Classify a raw statement + parsed expression into a typed Statement.
1977
+ * Determined at parse time from token type + expression shape.
1978
+ */
1979
+ function classifyStatement(raw, expr) {
1980
+ if (expr.k === "Comp" && expr.name === RESERVED_CALLS.Query) {
1981
+ const deps = collectQueryDeps(expr.args[1]);
1982
+ return {
1983
+ kind: "query",
1984
+ id: raw.id,
1985
+ call: {
1986
+ callee: RESERVED_CALLS.Query,
1987
+ args: expr.args
1988
+ },
1989
+ expr,
1990
+ deps: deps.length > 0 ? deps : void 0
1991
+ };
1992
+ }
1993
+ if (expr.k === "Comp" && expr.name === RESERVED_CALLS.Mutation) return {
1994
+ kind: "mutation",
1995
+ id: raw.id,
1996
+ call: {
1997
+ callee: RESERVED_CALLS.Mutation,
1998
+ args: expr.args
1999
+ },
2000
+ expr
2001
+ };
2002
+ if (raw.idTokenType === 18) return {
2003
+ kind: "state",
2004
+ id: raw.id,
2005
+ init: expr
2006
+ };
2007
+ return {
2008
+ kind: "value",
2009
+ id: raw.id,
2010
+ expr
2011
+ };
2012
+ }
2013
+ /**
2014
+ * Extract typed statements from the symbol table.
2015
+ * State defaults are materialized to plain values (no raw AST in output).
2016
+ */
2017
+ function extractStatements(stmts, ctx) {
2018
+ const stateDeclarations = {};
2019
+ const queryStatements = [];
2020
+ const mutationStatements = [];
2021
+ for (const stmt of stmts) switch (stmt.kind) {
2022
+ case "state":
2023
+ stateDeclarations[stmt.id] = materializeValue(stmt.init, ctx);
2024
+ break;
2025
+ case "query":
2026
+ queryStatements.push({
2027
+ statementId: stmt.id,
2028
+ toolAST: stmt.call.args[0] ?? null,
2029
+ argsAST: stmt.call.args[1] ?? null,
2030
+ defaultsAST: stmt.call.args[2] ?? null,
2031
+ refreshAST: stmt.call.args[3] ?? null,
2032
+ deps: stmt.deps,
2033
+ complete: true
2034
+ });
2035
+ break;
2036
+ case "mutation":
2037
+ mutationStatements.push({
2038
+ statementId: stmt.id,
2039
+ toolAST: stmt.call.args[0] ?? null,
2040
+ argsAST: stmt.call.args[1] ?? null
2041
+ });
2042
+ break;
2043
+ }
2044
+ for (const stmt of stmts) {
2045
+ const nodes = stmt.kind === "state" ? [stmt.init] : stmt.kind === "value" ? [stmt.expr] : stmt.kind === "query" || stmt.kind === "mutation" ? stmt.call.args : [];
2046
+ for (const node of nodes) for (const dep of collectQueryDeps(node)) if (!(dep in stateDeclarations)) stateDeclarations[dep] = null;
2047
+ }
2048
+ return {
2049
+ stateDeclarations,
2050
+ queryStatements,
2051
+ mutationStatements
2052
+ };
2053
+ }
2054
+ const DEFAULT_ROOT_STATEMENT_ID = "root";
2055
+ function isComponentStatement(stmt) {
2056
+ return stmt.kind === "value" && stmt.expr.k === "Comp" && !isBuiltin(stmt.expr.name) && stmt.expr.name !== RESERVED_CALLS.Query && stmt.expr.name !== RESERVED_CALLS.Mutation;
2057
+ }
2058
+ function pickEntryId(stmtMap, typedStmts, firstId, rootName) {
2059
+ if (stmtMap.has(DEFAULT_ROOT_STATEMENT_ID)) return DEFAULT_ROOT_STATEMENT_ID;
2060
+ if (rootName && stmtMap.has(rootName)) return rootName;
2061
+ const preferredComponent = rootName ? typedStmts.find((stmt) => isComponentStatement(stmt) && stmt.expr.name === rootName) : void 0;
2062
+ if (preferredComponent) return preferredComponent.id;
2063
+ return typedStmts.find(isComponentStatement)?.id ?? firstId;
2064
+ }
2065
+ function buildResult(stmtMap, typedStmts, firstId, wasIncomplete, stmtCount, cat, rootName) {
2066
+ const entryId = pickEntryId(stmtMap, typedStmts, firstId, rootName);
2067
+ if (!stmtMap.has(entryId)) return emptyResult(wasIncomplete);
2068
+ const syms = /* @__PURE__ */ new Map();
2069
+ for (const [id, stmt] of stmtMap) syms.set(id, stmt.kind === "state" ? stmt.init : stmt.expr);
2070
+ const unres = [];
2071
+ const errors = [];
2072
+ const unreached = /* @__PURE__ */ new Set();
2073
+ for (const [id, stmt] of stmtMap) {
2074
+ if (id === entryId) continue;
2075
+ if (stmt.kind === "state" || stmt.kind === "query" || stmt.kind === "mutation") continue;
2076
+ unreached.add(id);
2077
+ }
2078
+ const ctx = {
2079
+ syms,
2080
+ cat,
2081
+ errors,
2082
+ unres,
2083
+ visited: /* @__PURE__ */ new Set(),
2084
+ partial: wasIncomplete,
2085
+ currentStatementId: entryId,
2086
+ unreached
2087
+ };
2088
+ const materialized = materializeValue(syms.get(entryId), ctx);
2089
+ const root = isElementNode(materialized) ? materialized : null;
2090
+ if (root) root.statementId = entryId;
2091
+ const { stateDeclarations, queryStatements, mutationStatements } = extractStatements(typedStmts, ctx);
2092
+ return {
2093
+ root,
2094
+ meta: {
2095
+ incomplete: wasIncomplete,
2096
+ unresolved: unres,
2097
+ orphaned: [...unreached],
2098
+ statementCount: stmtCount,
2099
+ errors
2100
+ },
2101
+ stateDeclarations,
2102
+ queryStatements,
2103
+ mutationStatements
2104
+ };
2105
+ }
2106
+ function skipString(input, start) {
2107
+ if (input[start] !== "\"") return start;
2108
+ let i = start + 1;
2109
+ while (i < input.length) {
2110
+ const c = input[i];
2111
+ if (c === "\\") i += 2;
2112
+ else if (c === "\"") return i + 1;
2113
+ else i++;
2114
+ }
2115
+ return i;
2116
+ }
2117
+ /** Extract code from markdown fences, or return as-is if no fences found.
2118
+ * String-context-aware: skips ``` inside double-quoted strings. */
2119
+ function stripFences(input) {
2120
+ const blocks = [];
2121
+ let i = 0;
2122
+ while (i < input.length) {
2123
+ let fenceStart = -1;
2124
+ while (i < input.length) {
2125
+ const nextI = skipString(input, i);
2126
+ if (nextI > i) {
2127
+ i = nextI;
2128
+ continue;
2129
+ }
2130
+ if (input[i] === "`" && i + 1 < input.length && input[i + 1] === "`" && i + 2 < input.length && input[i + 2] === "`") {
2131
+ fenceStart = i;
2132
+ break;
2133
+ }
2134
+ i++;
2135
+ }
2136
+ if (fenceStart === -1) break;
2137
+ let j = fenceStart + 3;
2138
+ while (j < input.length && input[j] !== "\n") j++;
2139
+ if (j >= input.length) {
2140
+ blocks.push(input.slice(fenceStart + 3).replace(/^[^\n]*\n?/, ""));
2141
+ i = input.length;
2142
+ break;
2143
+ }
2144
+ j++;
2145
+ let closePos = -1;
2146
+ let k = j;
2147
+ while (k < input.length) {
2148
+ const nextK = skipString(input, k);
2149
+ if (nextK > k) {
2150
+ k = nextK;
2151
+ continue;
2152
+ }
2153
+ if (input[k] === "`" && k + 1 < input.length && input[k + 1] === "`" && k + 2 < input.length && input[k + 2] === "`") {
2154
+ closePos = k;
2155
+ break;
2156
+ }
2157
+ k++;
2158
+ }
2159
+ if (closePos !== -1) {
2160
+ blocks.push(input.slice(j, closePos));
2161
+ i = closePos + 3;
2162
+ } else {
2163
+ blocks.push(input.slice(j));
2164
+ i = input.length;
2165
+ }
2166
+ }
2167
+ if (blocks.length > 0) return blocks.join("\n");
2168
+ if (input.startsWith("```")) {
2169
+ let j = 3;
2170
+ while (j < input.length && input[j] !== "\n") j++;
2171
+ const start = j < input.length ? j + 1 : 3;
2172
+ const body = input.slice(start);
2173
+ const trailingFence = body.lastIndexOf("```");
2174
+ if (trailingFence !== -1) return body.slice(0, trailingFence);
2175
+ return body;
2176
+ }
2177
+ return input;
2178
+ }
2179
+ /** Strip // and # line comments outside of strings (handles both " and ' delimiters). */
2180
+ function stripComments(input) {
2181
+ let inStr = false;
2182
+ return input.split("\n").map((line) => {
2183
+ for (let i = 0; i < line.length; i++) {
2184
+ const c = line[i];
2185
+ if (inStr) {
2186
+ if (c === "\\" && i + 1 < line.length) {
2187
+ i++;
2188
+ continue;
2189
+ }
2190
+ if (c === inStr) inStr = false;
2191
+ continue;
2192
+ }
2193
+ if (c === "\"" || c === "'") {
2194
+ inStr = c;
2195
+ continue;
2196
+ }
2197
+ if (c === "/" && line[i + 1] === "/") return line.substring(0, i).trimEnd();
2198
+ if (c === "#") return line.substring(0, i).trimEnd();
2199
+ }
2200
+ return line;
2201
+ }).join("\n");
2202
+ }
2203
+ /** Clean LLM response: strip fences, comments, whitespace. */
2204
+ function preprocess(input) {
2205
+ return stripComments(stripFences(input.trim())).trim();
2206
+ }
2207
+ function createStreamParser(cat, rootName) {
2208
+ let buf = "";
2209
+ let cleaned = "";
2210
+ let completedEnd = 0;
2211
+ const completedStmtMap = /* @__PURE__ */ new Map();
2212
+ let completedCount = 0;
2213
+ let firstId = "";
2214
+ function addStmt(text) {
2215
+ const t = text.trim();
2216
+ if (!t) return;
2217
+ for (const s of split(tokenize(t))) {
2218
+ const stmt = classifyStatement(s, parseExpression(s.tokens));
2219
+ completedStmtMap.set(s.id, stmt);
2220
+ completedCount++;
2221
+ if (!firstId) firstId = s.id;
2222
+ }
2223
+ }
2224
+ function refreshCleaned() {
2225
+ const next = preprocess(buf);
2226
+ if (!next.startsWith(cleaned.slice(0, completedEnd))) {
2227
+ completedEnd = 0;
2228
+ completedStmtMap.clear();
2229
+ completedCount = 0;
2230
+ firstId = "";
2231
+ }
2232
+ cleaned = next;
2233
+ }
2234
+ function scanNewCompleted() {
2235
+ let depth = 0, ternaryDepth = 0, inStr = false, esc = false;
2236
+ let stmtStart = completedEnd;
2237
+ for (let i = completedEnd; i < cleaned.length; i++) {
2238
+ const c = cleaned[i];
2239
+ if (esc) {
2240
+ esc = false;
2241
+ continue;
2242
+ }
2243
+ if (c === "\\" && inStr) {
2244
+ esc = true;
2245
+ continue;
2246
+ }
2247
+ if (inStr) {
2248
+ if (c === inStr) inStr = false;
2249
+ continue;
2250
+ }
2251
+ if (c === "\"" || c === "'") {
2252
+ inStr = c;
2253
+ continue;
2254
+ }
2255
+ if (c === "(" || c === "[" || c === "{") depth++;
2256
+ else if (c === ")" || c === "]" || c === "}") depth = Math.max(0, depth - 1);
2257
+ else if (c === "?" && depth === 0) ternaryDepth++;
2258
+ else if (c === ":" && depth === 0 && ternaryDepth > 0) ternaryDepth--;
2259
+ else if (c === "\n" && depth <= 0 && ternaryDepth <= 0) {
2260
+ let peek = i + 1;
2261
+ while (peek < cleaned.length && (cleaned[peek] === " " || cleaned[peek] === " " || cleaned[peek] === "\r" || cleaned[peek] === "\n")) peek++;
2262
+ if (peek < cleaned.length && (cleaned[peek] === "?" || cleaned[peek] === ":" && ternaryDepth > 0)) continue;
2263
+ const t = cleaned.slice(stmtStart, i).trim();
2264
+ if (t) addStmt(t);
2265
+ stmtStart = i + 1;
2266
+ completedEnd = i + 1;
2267
+ }
2268
+ }
2269
+ return stmtStart;
2270
+ }
2271
+ function currentResult() {
2272
+ refreshCleaned();
2273
+ const pendingStart = scanNewCompleted();
2274
+ const pendingText = cleaned.slice(pendingStart).trim();
2275
+ if (!pendingText) {
2276
+ if (completedCount === 0) return emptyResult();
2277
+ return buildResult(completedStmtMap, [...completedStmtMap.values()], firstId, false, completedCount, cat, rootName);
2278
+ }
2279
+ const { text: closed, wasIncomplete } = autoClose(pendingText);
2280
+ const stmts = split(tokenize(closed));
2281
+ if (!stmts.length) {
2282
+ if (completedCount === 0) return emptyResult(wasIncomplete);
2283
+ return buildResult(completedStmtMap, [...completedStmtMap.values()], firstId, wasIncomplete, completedCount, cat, rootName);
2284
+ }
2285
+ const allStmtMap = new Map(completedStmtMap);
2286
+ for (const s of stmts) {
2287
+ if (completedStmtMap.has(s.id)) continue;
2288
+ const stmt = classifyStatement(s, parseExpression(s.tokens));
2289
+ allStmtMap.set(s.id, stmt);
2290
+ }
2291
+ return buildResult(allStmtMap, [...allStmtMap.values()], firstId || stmts[0].id, wasIncomplete, completedCount + stmts.length, cat, rootName);
2292
+ }
2293
+ function reset() {
2294
+ buf = "";
2295
+ cleaned = "";
2296
+ completedEnd = 0;
2297
+ completedStmtMap.clear();
2298
+ completedCount = 0;
2299
+ firstId = "";
2300
+ }
2301
+ return {
2302
+ push(chunk) {
2303
+ buf += chunk;
2304
+ return currentResult();
2305
+ },
2306
+ set(fullText) {
2307
+ if (fullText.length < buf.length || !fullText.startsWith(buf)) reset();
2308
+ const delta = fullText.slice(buf.length);
2309
+ if (delta) buf += delta;
2310
+ return currentResult();
2311
+ },
2312
+ getResult: currentResult
2313
+ };
2314
+ }
2315
+ function getSchemaDefaultValue(property) {
2316
+ if (!property || typeof property !== "object" || Array.isArray(property)) return;
2317
+ return property.default;
2318
+ }
2319
+ function compileSchema(schema) {
2320
+ const map = /* @__PURE__ */ new Map();
2321
+ const defs = schema.$defs ?? {};
2322
+ for (const [name, def] of Object.entries(defs)) {
2323
+ const properties = def.properties ?? {};
2324
+ const required = def.required ?? [];
2325
+ const params = Object.keys(properties).map((key) => ({
2326
+ name: key,
2327
+ required: required.includes(key),
2328
+ defaultValue: getSchemaDefaultValue(properties[key])
2329
+ }));
2330
+ map.set(name, { params });
2331
+ }
2332
+ return map;
2333
+ }
2334
+ /**
2335
+ * Create a streaming parser from a library JSON Schema document.
2336
+ * Pass `library.toJSONSchema()` to get the schema.
2337
+ */
2338
+ function createStreamingParser(schema, rootName) {
2339
+ return createStreamParser(compileSchema(schema), rootName);
2340
+ }
2341
+ /** Build a signature hint like "Header(title*, subtitle, icon)" from JSON schema. */
2342
+ function buildSignatureHint(componentName, schema) {
2343
+ if (!schema?.properties) return void 0;
2344
+ const required = new Set(schema.required ?? []);
2345
+ return `Signature: ${componentName}(${Object.keys(schema.properties).map((k) => required.has(k) ? `${k}*` : k).join(", ")}) — * marks required`;
2346
+ }
2347
+ /**
2348
+ * Convert parser ValidationErrors into enriched OpenUIErrors with hints.
2349
+ *
2350
+ * Framework-agnostic — usable by React, Svelte, Vue, or standalone.
2351
+ */
2352
+ function enrichErrors(validationErrors, schema, componentNames) {
2353
+ return validationErrors.map((ve) => {
2354
+ const error = {
2355
+ source: "parser",
2356
+ code: ve.code,
2357
+ message: ve.message,
2358
+ component: ve.component,
2359
+ path: ve.path || void 0,
2360
+ statementId: ve.statementId
2361
+ };
2362
+ if (ve.code === "unknown-component" && componentNames.length) error.hint = `Available components: ${componentNames.join(", ")}`;
2363
+ else if (ve.code === "missing-required" || ve.code === "null-required") error.hint = buildSignatureHint(ve.component, schema.$defs?.[ve.component]);
2364
+ else if (ve.code === "inline-reserved") error.hint = `Declare as a top-level statement: myVar = ${ve.component}(...)`;
2365
+ return error;
2366
+ });
2367
+ }
2368
+ function isReactiveAssign(value) {
2369
+ return typeof value === "object" && value !== null && value.__reactive === "assign";
2370
+ }
2371
+ /**
2372
+ * Evaluate an AST node to a runtime value.
2373
+ */
2374
+ function evaluate(node, context, schemaCtx) {
2375
+ switch (node.k) {
2376
+ case "Str": return node.v;
2377
+ case "Num": return node.v;
2378
+ case "Bool": return node.v;
2379
+ case "Null": return null;
2380
+ case "Ph": return null;
2381
+ case "StateRef": return context.extraScope?.[node.n] ?? context.getState(node.n);
2382
+ case "Ref":
2383
+ case "RuntimeRef": return context.resolveRef(node.n);
2384
+ case "Arr": return node.els.map((el) => evaluate(el, context));
2385
+ case "Obj": return Object.fromEntries(node.entries.map(([k, v]) => [k, evaluate(v, context)]));
2386
+ case "Comp": {
2387
+ if (LAZY_BUILTINS.has(node.name)) return evaluateLazyBuiltin(node.name, node.args, context, schemaCtx);
2388
+ const builtin = BUILTINS[node.name];
2389
+ if (builtin) {
2390
+ const args = node.args.map((a) => evaluate(a, context));
2391
+ return builtin.fn(...args);
2392
+ }
2393
+ if (ACTION_NAMES.has(node.name)) return evaluateActionCall(node.name, node.args, context);
2394
+ if (node.mappedProps) {
2395
+ const def = schemaCtx?.library.components[node.name];
2396
+ const props = {};
2397
+ for (const [key, val] of Object.entries(node.mappedProps)) {
2398
+ const propSchema = def?.props?.shape?.[key];
2399
+ if (val.k === "StateRef" && propSchema && isReactiveSchema(propSchema)) props[key] = {
2400
+ __reactive: "assign",
2401
+ target: val.n,
2402
+ expr: {
2403
+ k: "StateRef",
2404
+ n: "$value"
2405
+ }
2406
+ };
2407
+ else if (val.k === "StateRef") props[key] = schemaCtx ? context.getState(val.n) : val;
2408
+ else props[key] = evaluate(val, context, schemaCtx);
2409
+ }
2410
+ const result = {
2411
+ type: "element",
2412
+ typeName: node.name,
2413
+ props,
2414
+ partial: false,
2415
+ hasDynamicProps: true
2416
+ };
2417
+ if (schemaCtx) {
2418
+ for (const [key, val] of Object.entries(props)) if (isElementNode(val)) props[key] = evaluateElementInline(val, context, schemaCtx);
2419
+ else if (Array.isArray(val)) props[key] = val.map((item) => isElementNode(item) ? evaluateElementInline(item, context, schemaCtx) : item);
2420
+ }
2421
+ return result;
2422
+ }
2423
+ console.warn(`[openui] Unexpected unmapped Comp node: ${node.name}`);
2424
+ return null;
2425
+ }
2426
+ case "BinOp": {
2427
+ if (node.op === "&&") {
2428
+ const left = evaluate(node.left, context);
2429
+ return left ? evaluate(node.right, context) : left;
2430
+ }
2431
+ if (node.op === "||") {
2432
+ const left = evaluate(node.left, context);
2433
+ return left ? left : evaluate(node.right, context);
2434
+ }
2435
+ const left = evaluate(node.left, context);
2436
+ const right = evaluate(node.right, context);
2437
+ switch (node.op) {
2438
+ case "+":
2439
+ if (typeof left === "string" || typeof right === "string") return String(left ?? "") + String(right ?? "");
2440
+ return toNumber(left) + toNumber(right);
2441
+ case "-": return toNumber(left) - toNumber(right);
2442
+ case "*": return toNumber(left) * toNumber(right);
2443
+ case "/": return toNumber(right) === 0 ? 0 : toNumber(left) / toNumber(right);
2444
+ case "%": return toNumber(right) === 0 ? 0 : toNumber(left) % toNumber(right);
2445
+ case "==": return left == right;
2446
+ case "!=": return left != right;
2447
+ case ">": return toNumber(left) > toNumber(right);
2448
+ case "<": return toNumber(left) < toNumber(right);
2449
+ case ">=": return toNumber(left) >= toNumber(right);
2450
+ case "<=": return toNumber(left) <= toNumber(right);
2451
+ default: return null;
2452
+ }
2453
+ }
2454
+ case "UnaryOp":
2455
+ if (node.op === "!") return !evaluate(node.operand, context);
2456
+ if (node.op === "-") return -toNumber(evaluate(node.operand, context));
2457
+ return null;
2458
+ case "Ternary": return evaluate(node.cond, context) ? evaluate(node.then, context) : evaluate(node.else, context);
2459
+ case "Member": {
2460
+ const obj = evaluate(node.obj, context);
2461
+ if (obj == null) return null;
2462
+ if (Array.isArray(obj)) {
2463
+ if (node.field === "length") return obj.length;
2464
+ return obj.map((item) => item?.[node.field] ?? null);
2465
+ }
2466
+ return obj[node.field];
2467
+ }
2468
+ case "Index": {
2469
+ const obj = evaluate(node.obj, context);
2470
+ const idx = evaluate(node.index, context);
2471
+ if (obj == null || idx == null) return null;
2472
+ if (Array.isArray(obj)) return obj[toNumber(idx)];
2473
+ return obj[String(idx)];
2474
+ }
2475
+ case "Assign": return {
2476
+ __reactive: "assign",
2477
+ target: node.target,
2478
+ expr: node.value
2479
+ };
2480
+ }
2481
+ }
2482
+ /**
2483
+ * Evaluate an ElementNode's props with schema awareness. Used by evaluate()
2484
+ * when schema context is available and a Comp produces an ElementNode that
2485
+ * needs its own props evaluated with reactive schema detection.
2486
+ */
2487
+ function evaluateElementInline(el, context, schemaCtx) {
2488
+ if (el.hasDynamicProps === false) return el;
2489
+ const def = schemaCtx.library.components[el.typeName];
2490
+ const evaluated = {};
2491
+ for (const [key, value] of Object.entries(el.props)) {
2492
+ const propSchema = def?.props?.shape?.[key];
2493
+ evaluated[key] = evaluatePropInline(value, context, schemaCtx, propSchema);
2494
+ }
2495
+ return {
2496
+ ...el,
2497
+ props: evaluated
2498
+ };
2499
+ }
2500
+ /**
2501
+ * Evaluate a single prop value with schema awareness.
2502
+ * Delegates to shared evaluatePropCore with inline-specific recursion callbacks.
2503
+ */
2504
+ function evaluatePropInline(value, context, schemaCtx, reactiveSchema) {
2505
+ return evaluatePropCore(value, context, schemaCtx, reactiveSchema, {
2506
+ recurseElement: (el) => evaluateElementInline(el, context, schemaCtx),
2507
+ recurse: (v, rs) => evaluatePropInline(v, context, schemaCtx, rs)
2508
+ });
2509
+ }
2510
+ /** Convert a resolved runtime value back to a literal AST node for deferred evaluation. */
2511
+ function toLiteralAST(value) {
2512
+ if (value === null || value === void 0) return { k: "Null" };
2513
+ if (typeof value === "string") return {
2514
+ k: "Str",
2515
+ v: value
2516
+ };
2517
+ if (typeof value === "number") return {
2518
+ k: "Num",
2519
+ v: value
2520
+ };
2521
+ if (typeof value === "boolean") return {
2522
+ k: "Bool",
2523
+ v: value
2524
+ };
2525
+ if (Array.isArray(value)) return {
2526
+ k: "Arr",
2527
+ els: value.map(toLiteralAST)
2528
+ };
2529
+ if (typeof value === "object") return {
2530
+ k: "Obj",
2531
+ entries: Object.entries(value).map(([k, v]) => [k, toLiteralAST(v)])
2532
+ };
2533
+ return { k: "Null" };
2534
+ }
2535
+ /**
2536
+ * Evaluate Action/Run/ToAssistant/OpenUrl Comp nodes into ActionPlan/ActionStep values.
2537
+ */
2538
+ function evaluateActionCall(name, args, context) {
2539
+ switch (name) {
2540
+ case "Action": {
2541
+ const stepsArg = args.length > 0 ? evaluate(args[0], context) : [];
2542
+ return { steps: (Array.isArray(stepsArg) ? stepsArg : []).filter((s) => s != null && typeof s === "object" && "type" in s) };
2543
+ }
2544
+ case "Run": {
2545
+ if (args.length === 0) return null;
2546
+ const refNode = args[0];
2547
+ if (refNode.k === "RuntimeRef") return {
2548
+ type: ACTION_STEPS.Run,
2549
+ statementId: refNode.n,
2550
+ refType: refNode.refType
2551
+ };
2552
+ return null;
2553
+ }
2554
+ case "ToAssistant": {
2555
+ const message = args.length > 0 ? String(evaluate(args[0], context) ?? "") : "";
2556
+ const ctx = args.length > 1 ? String(evaluate(args[1], context) ?? "") : void 0;
2557
+ return {
2558
+ type: ACTION_STEPS.ToAssistant,
2559
+ message,
2560
+ context: ctx
2561
+ };
2562
+ }
2563
+ case "OpenUrl": {
2564
+ const url = args.length > 0 ? String(evaluate(args[0], context) ?? "") : "";
2565
+ return {
2566
+ type: ACTION_STEPS.OpenUrl,
2567
+ url
2568
+ };
2569
+ }
2570
+ case "Set": {
2571
+ if (args.length < 2) return null;
2572
+ const targetNode = args[0];
2573
+ if (targetNode.k !== "StateRef") return null;
2574
+ return {
2575
+ type: ACTION_STEPS.Set,
2576
+ target: targetNode.n,
2577
+ valueAST: args[1]
2578
+ };
2579
+ }
2580
+ case "Reset": {
2581
+ const targets = args.filter((a) => a.k === "StateRef").map((a) => a.n);
2582
+ if (targets.length === 0) return null;
2583
+ return {
2584
+ type: ACTION_STEPS.Reset,
2585
+ targets
2586
+ };
2587
+ }
2588
+ default: return null;
2589
+ }
2590
+ }
2591
+ /**
2592
+ * Substitute all Ref(varName) nodes in an AST tree with a literal value.
2593
+ * This pre-resolves loop variables so deferred expressions (like Action steps)
2594
+ * don't lose scope when evaluated later at click time.
2595
+ */
2596
+ function substituteRef(node, varName, value) {
2597
+ switch (node.k) {
2598
+ case "Ref": return node.n === varName ? toLiteralAST(value) : node;
2599
+ case "Member":
2600
+ if (isASTNode(node.obj)) {
2601
+ const subObj = substituteRef(node.obj, varName, value);
2602
+ if (subObj.k === "Obj") {
2603
+ const entry = subObj.entries.find(([k]) => k === node.field);
2604
+ if (entry) return entry[1];
2605
+ }
2606
+ return {
2607
+ ...node,
2608
+ obj: subObj
2609
+ };
2610
+ }
2611
+ return node;
2612
+ case "Index": return {
2613
+ ...node,
2614
+ obj: isASTNode(node.obj) ? substituteRef(node.obj, varName, value) : node.obj,
2615
+ index: isASTNode(node.index) ? substituteRef(node.index, varName, value) : node.index
2616
+ };
2617
+ case "BinOp": return {
2618
+ ...node,
2619
+ left: substituteRef(node.left, varName, value),
2620
+ right: substituteRef(node.right, varName, value)
2621
+ };
2622
+ case "UnaryOp": return {
2623
+ ...node,
2624
+ operand: substituteRef(node.operand, varName, value)
2625
+ };
2626
+ case "Ternary": return {
2627
+ ...node,
2628
+ cond: substituteRef(node.cond, varName, value),
2629
+ then: substituteRef(node.then, varName, value),
2630
+ else: substituteRef(node.else, varName, value)
2631
+ };
2632
+ case "Arr": return {
2633
+ ...node,
2634
+ els: node.els.map((e) => substituteRef(e, varName, value))
2635
+ };
2636
+ case "Obj": return {
2637
+ ...node,
2638
+ entries: node.entries.map(([k, v]) => [k, substituteRef(v, varName, value)])
2639
+ };
2640
+ case "Comp": {
2641
+ const result = {
2642
+ ...node,
2643
+ args: node.args.map((a) => substituteRef(a, varName, value))
2644
+ };
2645
+ if (node.mappedProps) {
2646
+ const subProps = {};
2647
+ for (const [k, v] of Object.entries(node.mappedProps)) subProps[k] = substituteRef(v, varName, value);
2648
+ result.mappedProps = subProps;
2649
+ }
2650
+ return result;
2651
+ }
2652
+ case "Assign": return {
2653
+ ...node,
2654
+ value: substituteRef(node.value, varName, value)
2655
+ };
2656
+ default: return node;
2657
+ }
2658
+ }
2659
+ /**
2660
+ * Each(array, varName, template) — evaluate template once per array item.
2661
+ * varName is user-defined (e.g. `issue`, `ticket`) — no $ prefix collision.
2662
+ *
2663
+ * Before evaluation, substitutes all Ref(varName) in the template with the
2664
+ * current item's literal value. This ensures deferred expressions (like
2665
+ * Action/Set steps) capture concrete values instead of dangling loop refs.
2666
+ */
2667
+ function evaluateLazyBuiltin(name, args, context, schemaCtx) {
2668
+ if (name === "Each") {
2669
+ if (args.length < 3) return [];
2670
+ const arr = evaluate(args[0], context);
2671
+ if (!Array.isArray(arr)) return [];
2672
+ const varName = args[1].k === "Ref" ? args[1].n : args[1].k === "Str" ? args[1].v : null;
2673
+ if (!varName) return [];
2674
+ const template = args[2];
2675
+ return arr.map((item, _idx) => {
2676
+ const substituted = substituteRef(template, varName, item);
2677
+ const childCtx = {
2678
+ ...context,
2679
+ resolveRef: (refName) => {
2680
+ if (refName === varName) return item;
2681
+ return context.resolveRef(refName);
2682
+ }
2683
+ };
2684
+ const result = evaluate(substituted, childCtx, schemaCtx);
2685
+ if (schemaCtx && isElementNode(result)) return evaluateElementInline(result, childCtx, schemaCtx);
2686
+ return result;
2687
+ });
2688
+ }
2689
+ return null;
2690
+ }
2691
+ /**
2692
+ * Shared prop value evaluation logic.
2693
+ *
2694
+ * Both evaluator.ts (inline path) and evaluate-tree.ts (React path) need
2695
+ * identical prop evaluation — AST resolution, ReactiveAssign handling,
2696
+ * ElementNode recursion, ActionPlan preservation. The only difference is
2697
+ * how they recurse into ElementNodes. This module extracts the shared core
2698
+ * and takes recursion callbacks so each caller can supply its own strategy.
2699
+ *
2700
+ * Also fixes the nested reactive drop bug: reactiveSchema is now correctly
2701
+ * passed through plain object recursion.
2702
+ */
2703
+ /**
2704
+ * Evaluate a single prop value with schema awareness. Handles AST nodes,
2705
+ * ReactiveAssign markers, nested ElementNodes, arrays, and ActionPlans.
2706
+ */
2707
+ function evaluatePropCore(value, context, schemaCtx, reactiveSchema, callbacks) {
2708
+ if (value == null) return value;
2709
+ if (typeof value !== "object") return value;
2710
+ if (isASTNode(value)) {
2711
+ if (value.k === "StateRef" && reactiveSchema && isReactiveSchema(reactiveSchema)) return {
2712
+ __reactive: "assign",
2713
+ target: value.n,
2714
+ expr: {
2715
+ k: "StateRef",
2716
+ n: "$value"
2717
+ }
2718
+ };
2719
+ const result = evaluate(value, context, schemaCtx);
2720
+ if (isElementNode(result)) return callbacks.recurseElement(result);
2721
+ if (Array.isArray(result)) return result.map((item) => isElementNode(item) ? callbacks.recurseElement(item) : item);
2722
+ if (isReactiveAssign(result) && !(reactiveSchema && isReactiveSchema(reactiveSchema))) return context.getState(result.target) ?? null;
2723
+ return result;
2724
+ }
2725
+ if (typeof value === "string" && reactiveSchema && isReactiveSchema(reactiveSchema)) return value;
2726
+ if (Array.isArray(value)) return value.map((v) => callbacks.recurse(v, reactiveSchema));
2727
+ if (isElementNode(value)) return callbacks.recurseElement(value);
2728
+ const obj = value;
2729
+ if ("steps" in obj && Array.isArray(obj.steps)) return value;
2730
+ if ("type" in obj && "valueAST" in obj) return value;
2731
+ let needsEval = false;
2732
+ for (const val of Object.values(obj)) if (typeof val === "object" && val !== null) {
2733
+ needsEval = true;
2734
+ break;
2735
+ }
2736
+ if (needsEval) {
2737
+ const result = {};
2738
+ for (const [k, v] of Object.entries(obj)) result[k] = callbacks.recurse(v, reactiveSchema);
2739
+ return result;
2740
+ }
2741
+ return value;
2742
+ }
2743
+ /**
2744
+ * Evaluate all AST nodes in an ElementNode tree's props.
2745
+ * Returns a new ElementNode with all props resolved to concrete values.
2746
+ *
2747
+ * Uses the unified evaluator with schema context for reactive-aware evaluation.
2748
+ */
2749
+ function evaluateElementProps(el, evalCtx) {
2750
+ if (el.hasDynamicProps === false) return el;
2751
+ const schemaCtx = { library: evalCtx.library };
2752
+ const def = evalCtx.library.components[el.typeName];
2753
+ const evaluated = {};
2754
+ for (const [key, value] of Object.entries(el.props)) {
2755
+ const propSchema = def?.props?.shape?.[key];
2756
+ try {
2757
+ evaluated[key] = evaluatePropValue(value, evalCtx, schemaCtx, propSchema);
2758
+ } catch (e) {
2759
+ evaluated[key] = value;
2760
+ const msg = e instanceof Error ? e.message : String(e);
2761
+ evalCtx.errors?.push({
2762
+ source: "runtime",
2763
+ code: "runtime-error",
2764
+ component: el.typeName,
2765
+ statementId: el.statementId,
2766
+ message: `Evaluating prop "${key}" on ${el.typeName} failed: ${msg}`,
2767
+ hint: `Check the expression used for prop "${key}"`
2768
+ });
2769
+ }
2770
+ }
2771
+ return {
2772
+ ...el,
2773
+ props: evaluated
2774
+ };
2775
+ }
2776
+ /**
2777
+ * Evaluate a single prop value with schema awareness.
2778
+ * Delegates to shared evaluatePropCore with evaluate-tree-specific recursion callbacks.
2779
+ */
2780
+ function evaluatePropValue(value, evalCtx, schemaCtx, reactiveSchema) {
2781
+ return evaluatePropCore(value, evalCtx.ctx, schemaCtx, reactiveSchema, {
2782
+ recurseElement: (el) => evaluateElementProps(el, evalCtx),
2783
+ recurse: (v, rs) => evaluatePropValue(v, evalCtx, schemaCtx, rs)
2784
+ });
2785
+ }
2786
+ /**
2787
+ * MCP utilities — type definitions and result extraction for MCP client integration.
2788
+ *
2789
+ * The Renderer accepts an MCP client directly as `toolProvider`.
2790
+ * It detects the MCP client shape (has `callTool({ name, arguments })`) and
2791
+ * wraps responses with `extractToolResult` automatically.
2792
+ *
2793
+ * @example
2794
+ * ```tsx
2795
+ * import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2796
+ * import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
2797
+ *
2798
+ * const client = new Client({ name: "my-app", version: "1.0.0" });
2799
+ * await client.connect(new StreamableHTTPClientTransport(new URL("/api/mcp")));
2800
+ *
2801
+ * // Pass directly — Renderer handles MCP response extraction
2802
+ * <Renderer toolProvider={client} library={library} response={content} />
2803
+ * ```
2804
+ */
2805
+ /**
2806
+ * Error thrown when an MCP tool call returns `isError: true`.
2807
+ * Preserves the raw error content from the MCP response for structured handling.
2808
+ */
2809
+ var McpToolError = class extends Error {
2810
+ toolErrorText;
2811
+ constructor(errorText) {
2812
+ super(`MCP tool error: ${errorText || "Unknown error"}`);
2813
+ this.name = "McpToolError";
2814
+ this.toolErrorText = errorText;
2815
+ }
2816
+ };
2817
+ /**
2818
+ * Extract the actual data from an MCP callTool result.
2819
+ * Prefers structuredContent (machine-readable JSON), falls back to parsing text content.
2820
+ */
2821
+ function extractToolResult(result) {
2822
+ if (result.isError) {
2823
+ const errorText = result.content?.filter((c) => c.type === "text").map((c) => c.text).join("\n");
2824
+ throw new McpToolError(errorText || "Unknown error");
2825
+ }
2826
+ if (result.structuredContent != null) return result.structuredContent;
2827
+ const textParts = result.content?.filter((c) => c.type === "text").map((c) => c.text ?? "");
2828
+ if (textParts?.length) {
2829
+ const text = textParts.join("");
2830
+ try {
2831
+ return JSON.parse(text);
2832
+ } catch {
2833
+ return text;
2834
+ }
2835
+ }
2836
+ return null;
2837
+ }
2838
+ /**
2839
+ * Standard error thrown when a tool name is not found in a function-map ToolProvider.
2840
+ * Used by Renderer's inline normalization to give clear error messages.
2841
+ */
2842
+ var ToolNotFoundError = class extends Error {
2843
+ toolName;
2844
+ availableTools;
2845
+ constructor(toolName, availableTools = []) {
2846
+ super(`[openui] No handler for tool "${toolName}". Available: ${availableTools.join(", ") || "(none)"}`);
2847
+ this.name = "ToolNotFoundError";
2848
+ this.toolName = toolName;
2849
+ this.availableTools = availableTools;
2850
+ }
2851
+ };
2852
+ /** JSON.stringify with stable key ordering at all nesting levels. */
2853
+ function stableStringify(value) {
2854
+ return JSON.stringify(value, (_key, val) => {
2855
+ if (val && typeof val === "object" && !Array.isArray(val)) {
2856
+ const sorted = {};
2857
+ for (const k of Object.keys(val).sort()) sorted[k] = val[k];
2858
+ return sorted;
2859
+ }
2860
+ if (val === void 0) return "__undefined__";
2861
+ if (typeof val === "number") {
2862
+ if (Number.isNaN(val)) return "__NaN__";
2863
+ if (val === Infinity) return "__Inf__";
2864
+ if (val === -Infinity) return "__-Inf__";
2865
+ }
2866
+ return val;
2867
+ });
2868
+ }
2869
+ function buildCacheKey(toolName, args, deps) {
2870
+ const depsKey = deps != null ? "::" + stableStringify(deps) : "";
2871
+ return toolName + "::" + stableStringify(args) + depsKey;
2872
+ }
2873
+ function createQueryManager(toolProvider) {
2874
+ const queries = /* @__PURE__ */ new Map();
2875
+ const mutations = /* @__PURE__ */ new Map();
2876
+ const cache = /* @__PURE__ */ new Map();
2877
+ const listeners = /* @__PURE__ */ new Set();
2878
+ let snapshot = {
2879
+ __openui_loading: [],
2880
+ __openui_refetching: [],
2881
+ __openui_errors: []
2882
+ };
2883
+ let snapshotJson = JSON.stringify(snapshot);
2884
+ let disposed = false;
2885
+ let generation = 0;
2886
+ function rebuildSnapshot() {
2887
+ const out = {
2888
+ __openui_loading: [],
2889
+ __openui_refetching: [],
2890
+ __openui_errors: []
2891
+ };
2892
+ for (const [sid, q] of queries) {
2893
+ const entry = cache.get(q.cacheKey);
2894
+ if (entry && entry.data !== void 0) out[sid] = entry.data;
2895
+ else if (q.prevCacheKey) {
2896
+ const prev = cache.get(q.prevCacheKey);
2897
+ if (prev && prev.data !== void 0) out[sid] = prev.data;
2898
+ else out[sid] = q.defaults;
2899
+ } else out[sid] = q.defaults;
2900
+ if (q.loading) {
2901
+ out.__openui_loading.push(sid);
2902
+ if (q.everFetched) out.__openui_refetching.push(sid);
2903
+ }
2904
+ if (q.error) out.__openui_errors.push(q.error);
2905
+ }
2906
+ for (const [sid, m] of mutations) {
2907
+ out[sid] = m.result;
2908
+ if (m.error) out.__openui_errors.push(m.error);
2909
+ }
2910
+ try {
2911
+ const outJson = JSON.stringify(out);
2912
+ if (outJson === snapshotJson) return false;
2913
+ snapshot = out;
2914
+ snapshotJson = outJson;
2915
+ } catch {
2916
+ snapshot = out;
2917
+ snapshotJson = "";
2918
+ }
2919
+ return true;
2920
+ }
2921
+ function notify() {
2922
+ for (const listener of [...listeners]) listener();
2923
+ }
2924
+ async function executeFetch(cacheKey, statementId) {
2925
+ if (!toolProvider) return;
2926
+ const q = queries.get(statementId);
2927
+ if (!q) return;
2928
+ const fetchKey = cacheKey;
2929
+ const toolName = q.toolName;
2930
+ const args = q.args;
2931
+ let entry = cache.get(fetchKey);
2932
+ if (!entry) {
2933
+ entry = {
2934
+ data: void 0,
2935
+ inFlight: true
2936
+ };
2937
+ cache.set(fetchKey, entry);
2938
+ } else entry.inFlight = true;
2939
+ q.loading = true;
2940
+ rebuildSnapshot();
2941
+ notify();
2942
+ try {
2943
+ const data = await toolProvider.callTool(toolName, args ?? {});
2944
+ if (disposed) return;
2945
+ const current = queries.get(statementId);
2946
+ if (!current || current.cacheKey !== fetchKey) {
2947
+ entry.inFlight = false;
2948
+ return;
2949
+ }
2950
+ entry.data = data ?? null;
2951
+ current.everFetched = true;
2952
+ current.error = void 0;
2953
+ if (current.prevCacheKey && current.prevCacheKey !== fetchKey) {
2954
+ const prevKey = current.prevCacheKey;
2955
+ current.prevCacheKey = void 0;
2956
+ cleanupCacheEntry(prevKey);
2957
+ }
2958
+ } catch (err) {
2959
+ const current = queries.get(statementId);
2960
+ if (current && current.cacheKey === fetchKey) if (err instanceof ToolNotFoundError) current.error = {
2961
+ source: "query",
2962
+ code: "tool-not-found",
2963
+ message: `Query tool "${toolName}" not found`,
2964
+ statementId,
2965
+ component: "Query",
2966
+ toolName,
2967
+ hint: err.availableTools.length ? `Available tools: ${err.availableTools.join(", ")}` : void 0
2968
+ };
2969
+ else if (err instanceof McpToolError) current.error = {
2970
+ source: "query",
2971
+ code: "mcp-error",
2972
+ message: `Query "${toolName}" returned an error: ${err.toolErrorText}`,
2973
+ statementId,
2974
+ component: "Query",
2975
+ toolName
2976
+ };
2977
+ else current.error = {
2978
+ source: "query",
2979
+ code: "tool-error",
2980
+ message: `Query "${toolName}" failed: ${err instanceof Error ? err.message : String(err)}`,
2981
+ statementId,
2982
+ component: "Query",
2983
+ toolName
2984
+ };
2985
+ console.error(`Query "${toolName}" failed:`, err);
2986
+ } finally {
2987
+ entry.inFlight = false;
2988
+ const current = queries.get(statementId);
2989
+ if (current && current.cacheKey === fetchKey) {
2990
+ current.loading = false;
2991
+ if (rebuildSnapshot()) notify();
2992
+ if (current.needsRefetch) {
2993
+ current.needsRefetch = false;
2994
+ executeFetch(current.cacheKey, statementId);
2995
+ }
2996
+ } else if (rebuildSnapshot()) notify();
2997
+ }
2998
+ }
2999
+ /** Remove a cache entry if no query references it. */
3000
+ function cleanupCacheEntry(cacheKey) {
3001
+ for (const q of queries.values()) if (q.cacheKey === cacheKey || q.prevCacheKey === cacheKey) return;
3002
+ cache.delete(cacheKey);
3003
+ }
3004
+ function evaluateQueries(queryNodes) {
3005
+ if (disposed) return;
3006
+ const activeIds = new Set(queryNodes.map((n) => n.statementId));
3007
+ for (const [sid, q] of queries) if (!activeIds.has(sid)) {
3008
+ if (q.timer) clearInterval(q.timer);
3009
+ queries.delete(sid);
3010
+ cleanupCacheEntry(q.cacheKey);
3011
+ if (q.prevCacheKey) cleanupCacheEntry(q.prevCacheKey);
3012
+ }
3013
+ for (const node of queryNodes) {
3014
+ if (!node.complete) continue;
3015
+ const cacheKey = buildCacheKey(node.toolName, node.args, node.deps);
3016
+ const existing = queries.get(node.statementId);
3017
+ if (existing) {
3018
+ if (existing.cacheKey !== cacheKey) existing.prevCacheKey = existing.cacheKey;
3019
+ existing.toolName = node.toolName;
3020
+ existing.args = node.args;
3021
+ existing.defaults = node.defaults;
3022
+ existing.cacheKey = cacheKey;
3023
+ } else queries.set(node.statementId, {
3024
+ toolName: node.toolName,
3025
+ args: node.args,
3026
+ defaults: node.defaults,
3027
+ cacheKey,
3028
+ loading: false,
3029
+ everFetched: false,
3030
+ refreshInterval: 0,
3031
+ needsRefetch: false
3032
+ });
3033
+ const q = queries.get(node.statementId);
3034
+ const entry = cache.get(cacheKey);
3035
+ const hasSettledData = entry && entry.data !== void 0 && !entry.inFlight;
3036
+ if (toolProvider && !hasSettledData && !entry?.inFlight) executeFetch(cacheKey, node.statementId);
3037
+ const newInterval = node.refreshInterval ?? 0;
3038
+ if (newInterval !== q.refreshInterval) {
3039
+ if (q.timer) {
3040
+ clearInterval(q.timer);
3041
+ q.timer = void 0;
3042
+ }
3043
+ if (newInterval > 0) q.timer = setInterval(() => {
3044
+ if (disposed || !toolProvider) return;
3045
+ if (!cache.get(q.cacheKey)?.inFlight) executeFetch(q.cacheKey, node.statementId);
3046
+ }, newInterval * 1e3);
3047
+ q.refreshInterval = newInterval;
3048
+ }
3049
+ }
3050
+ if (rebuildSnapshot()) notify();
3051
+ }
3052
+ function getResult(statementId) {
3053
+ const q = queries.get(statementId);
3054
+ if (!q) return null;
3055
+ const entry = cache.get(q.cacheKey);
3056
+ if (entry && entry.data !== void 0) return entry.data;
3057
+ if (q.prevCacheKey) {
3058
+ const prev = cache.get(q.prevCacheKey);
3059
+ if (prev && prev.data !== void 0) return prev.data;
3060
+ }
3061
+ return q.defaults;
3062
+ }
3063
+ function isLoading(statementId) {
3064
+ return queries.get(statementId)?.loading ?? false;
3065
+ }
3066
+ function isAnyLoading() {
3067
+ for (const q of queries.values()) if (q.loading) return true;
3068
+ return false;
3069
+ }
3070
+ function invalidate(statementIds) {
3071
+ if (disposed || !toolProvider) return;
3072
+ const targets = statementIds?.length ? statementIds.filter((sid) => queries.has(sid)) : [...queries.keys()];
3073
+ for (const sid of targets) {
3074
+ const q = queries.get(sid);
3075
+ if (!q) continue;
3076
+ if (cache.get(q.cacheKey)?.inFlight) q.needsRefetch = true;
3077
+ else executeFetch(q.cacheKey, sid);
3078
+ }
3079
+ }
3080
+ function registerMutations(nodes) {
3081
+ const activeIds = new Set(nodes.map((n) => n.statementId));
3082
+ for (const sid of mutations.keys()) if (!activeIds.has(sid)) mutations.delete(sid);
3083
+ for (const node of nodes) {
3084
+ const existing = mutations.get(node.statementId);
3085
+ if (existing) {
3086
+ if (existing.toolName !== node.toolName) {
3087
+ existing.toolName = node.toolName;
3088
+ existing.result = {
3089
+ status: "idle",
3090
+ data: null,
3091
+ error: null
3092
+ };
3093
+ existing.error = void 0;
3094
+ }
3095
+ } else mutations.set(node.statementId, {
3096
+ toolName: node.toolName,
3097
+ result: { status: "idle" }
3098
+ });
3099
+ }
3100
+ if (rebuildSnapshot()) notify();
3101
+ }
3102
+ async function fireMutation(statementId, evaluatedArgs, refreshQueryIds) {
3103
+ if (disposed || !toolProvider) return false;
3104
+ const m = mutations.get(statementId);
3105
+ if (!m) return false;
3106
+ if (m.result.status === "loading") return false;
3107
+ const gen = generation;
3108
+ m.result = { status: "loading" };
3109
+ rebuildSnapshot();
3110
+ notify();
3111
+ let success = false;
3112
+ try {
3113
+ const data = await toolProvider.callTool(m.toolName, evaluatedArgs);
3114
+ if (disposed || gen !== generation) return false;
3115
+ m.result = {
3116
+ status: "success",
3117
+ data
3118
+ };
3119
+ m.error = void 0;
3120
+ success = true;
3121
+ } catch (err) {
3122
+ if (disposed || gen !== generation) return false;
3123
+ const msg = err instanceof Error ? err.message : String(err);
3124
+ m.result = {
3125
+ status: "error",
3126
+ error: msg
3127
+ };
3128
+ if (err instanceof ToolNotFoundError) m.error = {
3129
+ source: "mutation",
3130
+ code: "tool-not-found",
3131
+ message: `Mutation tool "${m.toolName}" not found`,
3132
+ statementId,
3133
+ component: "Mutation",
3134
+ toolName: m.toolName,
3135
+ hint: err.availableTools.length ? `Available tools: ${err.availableTools.join(", ")}` : void 0
3136
+ };
3137
+ else if (err instanceof McpToolError) m.error = {
3138
+ source: "mutation",
3139
+ code: "mcp-error",
3140
+ message: `Mutation "${m.toolName}" returned an error: ${err.toolErrorText}`,
3141
+ statementId,
3142
+ component: "Mutation",
3143
+ toolName: m.toolName
3144
+ };
3145
+ else m.error = {
3146
+ source: "mutation",
3147
+ code: "tool-error",
3148
+ message: `Mutation "${m.toolName}" failed: ${msg}`,
3149
+ statementId,
3150
+ component: "Mutation",
3151
+ toolName: m.toolName
3152
+ };
3153
+ }
3154
+ rebuildSnapshot();
3155
+ notify();
3156
+ if (success && refreshQueryIds?.length) invalidate(refreshQueryIds);
3157
+ return success;
3158
+ }
3159
+ function getMutationResult(statementId) {
3160
+ return mutations.get(statementId)?.result ?? null;
3161
+ }
3162
+ function subscribe(listener) {
3163
+ listeners.add(listener);
3164
+ return () => listeners.delete(listener);
3165
+ }
3166
+ function getSnapshot() {
3167
+ return snapshot;
3168
+ }
3169
+ function activate() {
3170
+ disposed = false;
3171
+ }
3172
+ function dispose() {
3173
+ disposed = true;
3174
+ generation++;
3175
+ listeners.clear();
3176
+ for (const q of queries.values()) {
3177
+ if (q.timer) {
3178
+ clearInterval(q.timer);
3179
+ q.timer = void 0;
3180
+ }
3181
+ q.refreshInterval = 0;
3182
+ q.loading = false;
3183
+ q.needsRefetch = false;
3184
+ }
3185
+ mutations.clear();
3186
+ }
3187
+ return {
3188
+ evaluateQueries,
3189
+ getResult,
3190
+ isLoading,
3191
+ isAnyLoading,
3192
+ invalidate,
3193
+ registerMutations,
3194
+ fireMutation,
3195
+ getMutationResult,
3196
+ subscribe,
3197
+ getSnapshot,
3198
+ activate,
3199
+ dispose
3200
+ };
3201
+ }
3202
+ function createStore() {
3203
+ const state = /* @__PURE__ */ new Map();
3204
+ const listeners = /* @__PURE__ */ new Set();
3205
+ let snapshot = {};
3206
+ function notify() {
3207
+ const currentListeners = [...listeners];
3208
+ for (const listener of currentListeners) listener();
3209
+ }
3210
+ function rebuildSnapshot() {
3211
+ snapshot = Object.fromEntries(state);
3212
+ }
3213
+ function get(name) {
3214
+ return state.get(name);
3215
+ }
3216
+ function set(name, value) {
3217
+ const existing = state.get(name);
3218
+ if (Object.is(existing, value)) return;
3219
+ if (value && existing && typeof value === "object" && typeof existing === "object" && !Array.isArray(value) && !Array.isArray(existing)) {
3220
+ const nk = Object.keys(value);
3221
+ const ok = Object.keys(existing);
3222
+ if (nk.length === ok.length && nk.every((k) => Object.is(value[k], existing[k]))) return;
3223
+ }
3224
+ state.set(name, value);
3225
+ rebuildSnapshot();
3226
+ notify();
3227
+ }
3228
+ function subscribe(listener) {
3229
+ listeners.add(listener);
3230
+ return () => {
3231
+ listeners.delete(listener);
3232
+ };
3233
+ }
3234
+ function getSnapshot() {
3235
+ return snapshot;
3236
+ }
3237
+ function initialize(defaults, persisted) {
3238
+ for (const key of Object.keys(persisted)) state.set(key, persisted[key]);
3239
+ for (const key of Object.keys(defaults)) if (!state.has(key)) state.set(key, defaults[key]);
3240
+ rebuildSnapshot();
3241
+ notify();
3242
+ }
3243
+ function dispose() {
3244
+ state.clear();
3245
+ listeners.clear();
3246
+ snapshot = {};
3247
+ }
3248
+ return {
3249
+ get,
3250
+ set,
3251
+ subscribe,
3252
+ getSnapshot,
3253
+ initialize,
3254
+ dispose
3255
+ };
3256
+ }
3257
+
3258
+ //#endregion
3259
+ //#region ../../node_modules/.pnpm/@openuidev+react-lang@0.2.8_@modelcontextprotocol+sdk@1.29.0_zod@4.4.3__react@19.2.8_zod@4.4.3/node_modules/@openuidev/react-lang/dist/index.mjs
3260
+ function defineComponent(config) {
3261
+ return defineComponent$1(config);
3262
+ }
3263
+ function createLibrary(input) {
3264
+ return createLibrary$1(input);
3265
+ }
3266
+ const OpenUIContext = createContext(null);
3267
+ /**
3268
+ * Access the full OpenUI context. Throws if used outside a <Renderer />.
3269
+ */
3270
+ function useOpenUI() {
3271
+ const ctx = useContext(OpenUIContext);
3272
+ if (!ctx) throw new Error("useOpenUI must be used within a <Renderer /> component.");
3273
+ return ctx;
3274
+ }
3275
+ /**
3276
+ * Get the renderNode function for rendering nested component values.
3277
+ */
3278
+ function useRenderNode() {
3279
+ return useOpenUI().renderNode;
3280
+ }
3281
+ const FormNameContext = createContext(void 0);
3282
+ /** Unwrap { value, componentType } wrapper from form field entries. Returns raw value. */
3283
+ function unwrapFieldValue(v) {
3284
+ if (v && typeof v === "object" && !Array.isArray(v) && "value" in v) return v.value;
3285
+ return v;
3286
+ }
3287
+ /**
3288
+ * Core state hook — extracts all form state, action handling, parser
3289
+ * management, and context assembly out of the Renderer component.
3290
+ *
3291
+ * Store holds everything: $bindings as top-level keys, form fields nested
3292
+ * under formName as plain values.
3293
+ */
3294
+ function useOpenUIState({ response, library, isStreaming, onAction, onStateUpdate, initialState, toolProvider, onError }, renderDeep) {
3295
+ const sp = useMemo(() => createStreamingParser(library.toJSONSchema(), library.root), [library]);
3296
+ const parseExceptionRef = useRef(null);
3297
+ const result = useMemo(() => {
3298
+ parseExceptionRef.current = null;
3299
+ if (!response) return null;
3300
+ try {
3301
+ return sp.set(response);
3302
+ } catch (e) {
3303
+ parseExceptionRef.current = {
3304
+ source: "parser",
3305
+ code: "parse-exception",
3306
+ message: `Parser crashed: ${e instanceof Error ? e.message : String(e)}`,
3307
+ hint: "The response may contain syntax the parser cannot handle"
3308
+ };
3309
+ return null;
3310
+ }
3311
+ }, [sp, response]);
3312
+ const store = useMemo(() => createStore(), []);
3313
+ const queryManager = useMemo(() => createQueryManager(toolProvider ?? null), [toolProvider]);
3314
+ useEffect(() => {
3315
+ queryManager.activate();
3316
+ return () => queryManager.dispose();
3317
+ }, [queryManager]);
3318
+ const storeInitKeyRef = useRef(Symbol());
3319
+ useEffect(() => {
3320
+ if (!result?.stateDeclarations && !initialState) return;
3321
+ const key = `${JSON.stringify(result?.stateDeclarations)}::${JSON.stringify(initialState)}`;
3322
+ if (storeInitKeyRef.current === key) return;
3323
+ storeInitKeyRef.current = key;
3324
+ const bindingDefaults = {};
3325
+ if (initialState) for (const [key, value] of Object.entries(initialState)) if (key.startsWith("$")) bindingDefaults[key] = value;
3326
+ else store.set(key, value);
3327
+ store.initialize(result?.stateDeclarations ?? {}, bindingDefaults);
3328
+ }, [
3329
+ result?.stateDeclarations,
3330
+ store,
3331
+ initialState
3332
+ ]);
3333
+ const storeSnapshot = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);
3334
+ const querySnapshot = useSyncExternalStore(queryManager.subscribe, queryManager.getSnapshot, queryManager.getSnapshot);
3335
+ const evaluationContext = useMemo(() => ({
3336
+ getState: (name) => unwrapFieldValue(store.get(name)),
3337
+ resolveRef: (name) => {
3338
+ const mutResult = queryManager.getMutationResult(name);
3339
+ if (mutResult) return mutResult;
3340
+ return queryManager.getResult(name);
3341
+ }
3342
+ }), [store, queryManager]);
3343
+ useEffect(() => {
3344
+ if (isStreaming) return;
3345
+ const evaluatedNodes = (result?.queryStatements ?? []).map((qn) => {
3346
+ const relevantDeps = {};
3347
+ if (qn.deps) for (const ref of qn.deps) relevantDeps[ref] = storeSnapshot[ref];
3348
+ return {
3349
+ statementId: qn.statementId,
3350
+ toolName: qn.toolAST ? evaluate(qn.toolAST, evaluationContext) : "",
3351
+ args: qn.argsAST ? evaluate(qn.argsAST, evaluationContext) : null,
3352
+ defaults: qn.defaultsAST ? evaluate(qn.defaultsAST, evaluationContext) : null,
3353
+ refreshInterval: qn.refreshAST ? evaluate(qn.refreshAST, evaluationContext) : void 0,
3354
+ deps: Object.keys(relevantDeps).length > 0 ? relevantDeps : void 0,
3355
+ complete: qn.complete
3356
+ };
3357
+ });
3358
+ queryManager.evaluateQueries(evaluatedNodes);
3359
+ }, [
3360
+ isStreaming,
3361
+ result?.queryStatements,
3362
+ evaluationContext,
3363
+ queryManager,
3364
+ storeSnapshot
3365
+ ]);
3366
+ useEffect(() => {
3367
+ if (isStreaming) return;
3368
+ const nodes = (result?.mutationStatements ?? []).map((mn) => ({
3369
+ statementId: mn.statementId,
3370
+ toolName: mn.toolAST ? evaluate(mn.toolAST, evaluationContext) : ""
3371
+ }));
3372
+ queryManager.registerMutations(nodes);
3373
+ }, [
3374
+ isStreaming,
3375
+ result?.mutationStatements,
3376
+ evaluationContext,
3377
+ queryManager
3378
+ ]);
3379
+ const propsRef = useRef({
3380
+ onAction,
3381
+ onStateUpdate,
3382
+ onError
3383
+ });
3384
+ propsRef.current = {
3385
+ onAction,
3386
+ onStateUpdate,
3387
+ onError
3388
+ };
3389
+ const resultRef = useRef(result);
3390
+ resultRef.current = result;
3391
+ const lastInitSnapshotRef = useRef(null);
3392
+ useEffect(() => {
3393
+ lastInitSnapshotRef.current = store.getSnapshot();
3394
+ return store.subscribe(() => {
3395
+ const currentSnapshot = store.getSnapshot();
3396
+ if (currentSnapshot === lastInitSnapshotRef.current) return;
3397
+ lastInitSnapshotRef.current = null;
3398
+ propsRef.current.onStateUpdate?.(currentSnapshot);
3399
+ });
3400
+ }, [store]);
3401
+ const getFieldValue = useCallback((formName, name) => {
3402
+ if (!formName) return unwrapFieldValue(store.get(name));
3403
+ const formData = store.get(formName);
3404
+ if (!formData || typeof formData !== "object" || Array.isArray(formData)) return void 0;
3405
+ return unwrapFieldValue(formData[name]);
3406
+ }, [store]);
3407
+ const setFieldValue = useCallback((formName, componentType, name, value, shouldTriggerSaveCallback = true) => {
3408
+ const wrapped = {
3409
+ value,
3410
+ componentType
3411
+ };
3412
+ if (!formName) store.set(name, wrapped);
3413
+ else {
3414
+ const raw = store.get(formName);
3415
+ const formData = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
3416
+ store.set(formName, {
3417
+ ...formData,
3418
+ [name]: wrapped
3419
+ });
3420
+ }
3421
+ if (shouldTriggerSaveCallback) propsRef.current.onStateUpdate?.(store.getSnapshot());
3422
+ }, [store]);
3423
+ const getFormPayload = useCallback((formName) => {
3424
+ if (formName) {
3425
+ const raw = store.get(formName);
3426
+ if (raw && typeof raw === "object" && !Array.isArray(raw)) return { [formName]: raw };
3427
+ }
3428
+ return store.getSnapshot();
3429
+ }, [store]);
3430
+ const triggerAction = useCallback(async (userMessage, formName, action) => {
3431
+ const formPayload = getFormPayload(formName);
3432
+ const { onAction: handler } = propsRef.current;
3433
+ if (action && !("steps" in action)) {
3434
+ const actionType = action.type || BuiltinActionType.ContinueConversation;
3435
+ const params = { ...action.params || {} };
3436
+ if (action.url) params.url = action.url;
3437
+ if (action.context) params.context = action.context;
3438
+ handler?.({
3439
+ type: actionType,
3440
+ params,
3441
+ humanFriendlyMessage: userMessage,
3442
+ formState: formPayload,
3443
+ formName
3444
+ });
3445
+ return;
3446
+ }
3447
+ const actionPlan = action;
3448
+ if (actionPlan?.steps) {
3449
+ for (const step of actionPlan.steps) switch (step.type) {
3450
+ case ACTION_STEPS.Run:
3451
+ if (step.refType === "mutation") {
3452
+ const mn = resultRef.current?.mutationStatements?.find((m) => m.statementId === step.statementId);
3453
+ const evaluatedArgs = mn?.argsAST ? evaluate(mn.argsAST, evaluationContext) : {};
3454
+ if (!await queryManager.fireMutation(step.statementId, evaluatedArgs)) return;
3455
+ } else queryManager.invalidate([step.statementId]);
3456
+ break;
3457
+ case ACTION_STEPS.ToAssistant:
3458
+ handler?.({
3459
+ type: BuiltinActionType.ContinueConversation,
3460
+ params: step.context ? { context: step.context } : {},
3461
+ humanFriendlyMessage: step.message,
3462
+ formState: formPayload,
3463
+ formName
3464
+ });
3465
+ break;
3466
+ case ACTION_STEPS.OpenUrl:
3467
+ handler?.({
3468
+ type: BuiltinActionType.OpenUrl,
3469
+ params: { url: step.url },
3470
+ humanFriendlyMessage: "",
3471
+ formState: formPayload,
3472
+ formName
3473
+ });
3474
+ break;
3475
+ case ACTION_STEPS.Set: {
3476
+ if (!step.valueAST) {
3477
+ console.warn(`[openui] Set action for ${step.target} has no valueAST — skipping`);
3478
+ break;
3479
+ }
3480
+ const value = evaluate(step.valueAST, evaluationContext);
3481
+ store.set(step.target, value);
3482
+ break;
3483
+ }
3484
+ case ACTION_STEPS.Reset: {
3485
+ const decls = resultRef.current?.stateDeclarations ?? {};
3486
+ for (const target of step.targets) store.set(target, decls[target] ?? null);
3487
+ break;
3488
+ }
3489
+ }
3490
+ return;
3491
+ }
3492
+ handler?.({
3493
+ type: BuiltinActionType.ContinueConversation,
3494
+ params: {},
3495
+ humanFriendlyMessage: userMessage,
3496
+ formState: formPayload,
3497
+ formName
3498
+ });
3499
+ }, [
3500
+ queryManager,
3501
+ evaluationContext,
3502
+ getFormPayload,
3503
+ store
3504
+ ]);
3505
+ const renderErrorsRef = useRef([]);
3506
+ const isStreamingRef = useRef(isStreaming);
3507
+ isStreamingRef.current = isStreaming;
3508
+ const reportError = useCallback((error) => {
3509
+ if (isStreamingRef.current) return;
3510
+ renderErrorsRef.current.push(error);
3511
+ }, []);
3512
+ const isQueryLoading = querySnapshot.__openui_loading.length > 0;
3513
+ const contextValue = useMemo(() => ({
3514
+ library,
3515
+ renderNode: renderDeep,
3516
+ triggerAction,
3517
+ isStreaming,
3518
+ getFieldValue,
3519
+ setFieldValue,
3520
+ store,
3521
+ evaluationContext,
3522
+ reportError,
3523
+ isQueryLoading
3524
+ }), [
3525
+ library,
3526
+ renderDeep,
3527
+ isStreaming,
3528
+ isQueryLoading,
3529
+ triggerAction,
3530
+ getFieldValue,
3531
+ setFieldValue,
3532
+ store,
3533
+ evaluationContext,
3534
+ reportError
3535
+ ]);
3536
+ const runtimeErrorsRef = useRef([]);
3537
+ const evaluatedResult = useMemo(() => {
3538
+ if (!result?.root) return result;
3539
+ const errors = [];
3540
+ const evalCtx = {
3541
+ ctx: evaluationContext,
3542
+ library,
3543
+ store,
3544
+ errors
3545
+ };
3546
+ try {
3547
+ const evaluatedRoot = evaluateElementProps(result.root, evalCtx);
3548
+ runtimeErrorsRef.current = errors;
3549
+ return {
3550
+ ...result,
3551
+ root: evaluatedRoot
3552
+ };
3553
+ } catch (e) {
3554
+ const msg = e instanceof Error ? e.message : String(e);
3555
+ errors.push({
3556
+ source: "runtime",
3557
+ code: "runtime-error",
3558
+ message: `Prop evaluation failed: ${msg}`
3559
+ });
3560
+ runtimeErrorsRef.current = errors;
3561
+ return result;
3562
+ }
3563
+ }, [
3564
+ result,
3565
+ evaluationContext,
3566
+ library,
3567
+ store,
3568
+ storeSnapshot,
3569
+ querySnapshot
3570
+ ]);
3571
+ const lastErrorKeyRef = useRef("");
3572
+ useEffect(() => {
3573
+ if (isStreaming) {
3574
+ if (lastErrorKeyRef.current !== "") {
3575
+ lastErrorKeyRef.current = "";
3576
+ propsRef.current.onError?.([]);
3577
+ }
3578
+ return;
3579
+ }
3580
+ const errors = [];
3581
+ if (parseExceptionRef.current) errors.push(parseExceptionRef.current);
3582
+ if (response && !result?.root && !parseExceptionRef.current) errors.push({
3583
+ source: "parser",
3584
+ code: "parse-failed",
3585
+ message: result ? "Code parsed but produced no renderable root component" : "Response could not be parsed as valid openui-lang",
3586
+ hint: `The entire response must be valid openui-lang code starting with root = ${library.root ?? "Root"}(...)`
3587
+ });
3588
+ if (result?.meta?.errors?.length) errors.push(...enrichErrors(result.meta.errors, library.toJSONSchema(), Object.keys(library.components)));
3589
+ errors.push(...runtimeErrorsRef.current);
3590
+ errors.push(...renderErrorsRef.current);
3591
+ renderErrorsRef.current = [];
3592
+ errors.push(...querySnapshot.__openui_errors ?? []);
3593
+ const key = JSON.stringify(errors);
3594
+ if (key === lastErrorKeyRef.current) return;
3595
+ lastErrorKeyRef.current = key;
3596
+ if (propsRef.current.onError) propsRef.current.onError(errors);
3597
+ else if (errors.length > 0) for (const e of errors) console.warn(`[openui] ${e.source}/${e.code}: ${e.message}`);
3598
+ }, [
3599
+ isStreaming,
3600
+ response,
3601
+ result,
3602
+ evaluatedResult,
3603
+ querySnapshot,
3604
+ library
3605
+ ]);
3606
+ return {
3607
+ result: evaluatedResult,
3608
+ parseResult: result,
3609
+ contextValue,
3610
+ isQueryLoading
3611
+ };
3612
+ }
3613
+ /**
3614
+ * Error boundary that intentionally shows the last successfully rendered
3615
+ * children when a render error occurs. This "show last good state" behavior
3616
+ * prevents the UI from going blank during streaming or transient evaluation
3617
+ * errors, and auto-recovers when new valid children arrive.
3618
+ */
3619
+ var ElementErrorBoundary = class extends Component {
3620
+ lastValidChildren = null;
3621
+ constructor(props) {
3622
+ super(props);
3623
+ this.state = { hasError: false };
3624
+ }
3625
+ static getDerivedStateFromError() {
3626
+ return { hasError: true };
3627
+ }
3628
+ componentDidMount() {
3629
+ if (!this.state.hasError) this.lastValidChildren = this.props.children;
3630
+ }
3631
+ componentDidUpdate(prevProps) {
3632
+ if (!this.state.hasError) this.lastValidChildren = this.props.children;
3633
+ if (this.state.hasError && prevProps.children !== this.props.children) this.setState({ hasError: false });
3634
+ }
3635
+ componentDidCatch(error) {
3636
+ const name = this.props.componentName ?? "Unknown";
3637
+ this.props.onError?.({
3638
+ source: "runtime",
3639
+ code: "render-error",
3640
+ component: name,
3641
+ message: `Component ${name} render failed: ${error.message}`
3642
+ });
3643
+ }
3644
+ render() {
3645
+ if (this.state.hasError) return this.lastValidChildren;
3646
+ return this.props.children;
3647
+ }
3648
+ };
3649
+ /**
3650
+ * Recursively renders a parsed value (element, array, primitive)
3651
+ * into React nodes.
3652
+ */
3653
+ function renderDeep(value) {
3654
+ if (value == null) return null;
3655
+ if (typeof value === "string") return value;
3656
+ if (typeof value === "number") return String(value);
3657
+ if (typeof value === "boolean") return String(value);
3658
+ if (Array.isArray(value)) return value.map((v, i) => /* @__PURE__ */ jsx(Fragment, { children: renderDeep(v) }, i));
3659
+ if (typeof value === "object" && value !== null) {
3660
+ const obj = value;
3661
+ if (obj.type === "element") return /* @__PURE__ */ jsx(RenderNode, { node: obj });
3662
+ }
3663
+ return null;
3664
+ }
3665
+ /**
3666
+ * Renders a single ElementNode.
3667
+ */
3668
+ function RenderNode({ node }) {
3669
+ const { library, reportError } = useOpenUI();
3670
+ const Comp = library.components[node.typeName]?.component;
3671
+ if (!Comp) return null;
3672
+ return /* @__PURE__ */ jsx(ElementErrorBoundary, {
3673
+ componentName: node.typeName,
3674
+ onError: reportError,
3675
+ children: /* @__PURE__ */ jsx(RenderNodeInner, {
3676
+ el: node,
3677
+ Comp
3678
+ })
3679
+ });
3680
+ }
3681
+ /**
3682
+ * Renders a resolved element using its renderer.
3683
+ * Props are already evaluated by evaluate-tree — no AST awareness needed.
3684
+ */
3685
+ function RenderNodeInner({ el, Comp }) {
3686
+ const renderNode = useRenderNode();
3687
+ return /* @__PURE__ */ jsx(Comp, {
3688
+ props: el.props,
3689
+ renderNode,
3690
+ statementId: el.statementId
3691
+ });
3692
+ }
3693
+ let loadingStyleInjected = false;
3694
+ function ensureLoadingStyle() {
3695
+ if (loadingStyleInjected || typeof document === "undefined") return;
3696
+ loadingStyleInjected = true;
3697
+ const style = document.createElement("style");
3698
+ style.textContent = `@keyframes openui-spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }`;
3699
+ document.head.appendChild(style);
3700
+ }
3701
+ const DefaultQueryLoader = () => /* @__PURE__ */ jsx("div", { style: {
3702
+ position: "absolute",
3703
+ top: 8,
3704
+ right: 8,
3705
+ width: 16,
3706
+ height: 16,
3707
+ border: "2px solid #e5e7eb",
3708
+ borderTopColor: "#3b82f6",
3709
+ borderRadius: "50%",
3710
+ animation: "openui-spin 0.6s linear infinite",
3711
+ zIndex: 10
3712
+ } });
3713
+ function Renderer({ response, library, isStreaming = false, onAction, onStateUpdate, initialState, onParseResult, toolProvider, queryLoader, onError }) {
3714
+ useInsertionEffect(() => {
3715
+ ensureLoadingStyle();
3716
+ }, []);
3717
+ const onParseResultRef = useRef(onParseResult);
3718
+ onParseResultRef.current = onParseResult;
3719
+ const toolProviderInputRef = useRef(toolProvider);
3720
+ toolProviderInputRef.current = toolProvider;
3721
+ const stableToolProvider = useRef({ async callTool(toolName, args) {
3722
+ const current = toolProviderInputRef.current ?? null;
3723
+ if (current == null) throw new Error("[openui] toolProvider is null");
3724
+ if (typeof current.callTool === "function") return extractToolResult(await current.callTool({
3725
+ name: toolName,
3726
+ arguments: args
3727
+ }));
3728
+ const map = current;
3729
+ const fn = map[toolName];
3730
+ if (!fn) throw new ToolNotFoundError(toolName, Object.keys(map));
3731
+ return fn(args);
3732
+ } });
3733
+ const { result, parseResult, contextValue, isQueryLoading } = useOpenUIState({
3734
+ response,
3735
+ library,
3736
+ isStreaming,
3737
+ onAction,
3738
+ onStateUpdate,
3739
+ initialState,
3740
+ toolProvider: toolProvider != null ? stableToolProvider.current : null,
3741
+ onError
3742
+ }, renderDeep);
3743
+ useEffect(() => {
3744
+ onParseResultRef.current?.(parseResult);
3745
+ }, [parseResult]);
3746
+ if (!result?.root) return null;
3747
+ return /* @__PURE__ */ jsx(OpenUIContext.Provider, {
3748
+ value: contextValue,
3749
+ children: /* @__PURE__ */ jsxs("div", {
3750
+ style: { position: "relative" },
3751
+ children: [isQueryLoading && (queryLoader ?? /* @__PURE__ */ jsx(DefaultQueryLoader, {})), /* @__PURE__ */ jsx("div", {
3752
+ style: {
3753
+ opacity: isQueryLoading ? .7 : 1,
3754
+ transition: "opacity 0.2s ease"
3755
+ },
3756
+ children: /* @__PURE__ */ jsx(RenderNode, { node: result.root })
3757
+ })]
3758
+ })
3759
+ });
3760
+ }
3761
+ const FormValidationContext = createContext(null);
3762
+
3763
+ //#endregion
3764
+ //#region ../ui-react/dist/openUiLibrary-B8-Cvou9.js
3765
+ const propsFor = (props, container) => container ? props.extend({ children: z.array(z.any()).optional() }) : props;
3766
+ /**
3767
+ * catalog 的 OpenUI 投影:`defineComponent` 复用同一份 zod schema 与描述,
3768
+ * 渲染实现取自共享的 ui-kit 组件表,因此 OpenUI 档渲染的是我们的设计系统而不是 OpenUI 默认组件。
3769
+ */
3770
+ const webskillOpenUiLibrary = createLibrary({
3771
+ id: "webskill-shadcn",
3772
+ components: uiCatalog.components.map((def) => defineComponent({
3773
+ name: def.name,
3774
+ description: def.description,
3775
+ props: propsFor(def.props, def.children !== void 0),
3776
+ component: ((input) => catalogComponentImpls[def.name]?.({
3777
+ props: input.props,
3778
+ children: input.renderNode(input.props["children"])
3779
+ }) ?? null)
3780
+ }))
3781
+ });
3782
+ const OPEN_UI_COMPONENTS = Object.keys(webskillOpenUiLibrary.components);
3783
+
3784
+ //#endregion
3785
+ export { OPEN_UI_COMPONENTS, Renderer, webskillOpenUiLibrary };