@uicast/core 0.0.1-beta.2 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/CHANGELOG.md +3 -13
  2. package/README.md +126 -1
  3. package/dist/build-elements-by-key.d.ts +13 -0
  4. package/dist/build-elements-by-key.js +39 -0
  5. package/dist/constants.d.ts +2 -0
  6. package/dist/constants.js +4 -0
  7. package/dist/create-component-definition.d.ts +26 -0
  8. package/dist/create-component-definition.js +74 -0
  9. package/dist/entry-error.d.ts +41 -31
  10. package/dist/entry-error.js +49 -20
  11. package/dist/entry-shape.d.ts +7 -0
  12. package/dist/entry-shape.js +88 -0
  13. package/dist/expr/evaluate.d.ts +4 -6
  14. package/dist/expr/evaluate.js +36 -46
  15. package/dist/expr/extract-deps.d.ts +3 -1
  16. package/dist/expr/extract-deps.js +20 -20
  17. package/dist/expr/plan-step-waves.d.ts +8 -0
  18. package/dist/expr/plan-step-waves.js +45 -0
  19. package/dist/index.d.ts +7 -8
  20. package/dist/index.js +5 -9
  21. package/dist/internal.d.ts +12 -0
  22. package/dist/internal.js +13 -0
  23. package/dist/json-schema.d.ts +58 -0
  24. package/dist/json-schema.js +73 -0
  25. package/dist/prompt/format.d.ts +5 -0
  26. package/dist/prompt/format.js +22 -0
  27. package/dist/prompt/get-common-instructions-partial-prompt.d.ts +20 -10
  28. package/dist/prompt/get-common-instructions-partial-prompt.js +11 -9
  29. package/dist/prompt/get-components-partial-prompt.d.ts +20 -28
  30. package/dist/prompt/get-components-partial-prompt.js +122 -123
  31. package/dist/prompt/get-edit-request-prompt.d.ts +16 -16
  32. package/dist/prompt/get-edit-request-prompt.js +11 -18
  33. package/dist/prompt/get-error-recovery-prompt.d.ts +25 -14
  34. package/dist/prompt/get-error-recovery-prompt.js +17 -14
  35. package/dist/prompt/get-expressions-partial-prompt.d.ts +19 -20
  36. package/dist/prompt/get-expressions-partial-prompt.js +11 -21
  37. package/dist/prompt/get-functions-partial-prompt.d.ts +16 -16
  38. package/dist/prompt/get-functions-partial-prompt.js +52 -40
  39. package/dist/prompt/get-scope-partial-prompt.d.ts +17 -17
  40. package/dist/prompt/get-scope-partial-prompt.js +21 -29
  41. package/dist/prompt/index.js +0 -2
  42. package/dist/prompt/json-schema-to-ts.d.ts +6 -0
  43. package/dist/prompt/json-schema-to-ts.js +146 -0
  44. package/dist/prompt/md/EXPRESSIONS.json +1 -1
  45. package/dist/prompt/md/INSTRUCTIONS.json +1 -1
  46. package/dist/prompt/shared-types.d.ts +5 -0
  47. package/dist/prompt/shared-types.js +82 -0
  48. package/dist/scope/create-proxy-scope.d.ts +52 -17
  49. package/dist/scope/create-proxy-scope.js +280 -86
  50. package/dist/scope/parse-scope.d.ts +2 -20
  51. package/dist/scope/parse-scope.js +10 -35
  52. package/dist/scope/parse-set-address.d.ts +16 -0
  53. package/dist/scope/parse-set-address.js +52 -0
  54. package/dist/stream-json-lines.d.ts +14 -0
  55. package/dist/stream-json-lines.js +59 -0
  56. package/dist/types.d.ts +81 -46
  57. package/dist/types.js +6 -9
  58. package/dist/url-policy.d.ts +45 -0
  59. package/dist/url-policy.js +252 -0
  60. package/package.json +18 -16
  61. package/dist/def/create-component-definition.d.ts +0 -9
  62. package/dist/def/create-component-definition.js +0 -10
  63. package/dist/expr/analyze.d.ts +0 -5
  64. package/dist/expr/analyze.js +0 -189
  65. package/dist/expr/ast-utils.d.ts +0 -4
  66. package/dist/expr/ast-utils.js +0 -23
  67. package/dist/expr/globals.d.ts +0 -3
  68. package/dist/expr/globals.js +0 -111
  69. package/dist/expr/safer-eval.d.ts +0 -21
  70. package/dist/expr/safer-eval.js +0 -170
  71. package/dist/expr/validate.d.ts +0 -7
  72. package/dist/expr/validate.js +0 -109
  73. package/dist/prompt-utils/json-schema-to-ts.d.ts +0 -73
  74. package/dist/prompt-utils/json-schema-to-ts.js +0 -199
  75. package/dist/stream/stream-json-lines.d.ts +0 -1
  76. package/dist/stream/stream-json-lines.js +0 -94
  77. package/dist/utils/utils.d.ts +0 -13
  78. package/dist/utils/utils.js +0 -58
@@ -1,134 +1,133 @@
1
- import { JSONSchemaToTs } from "../prompt-utils/json-schema-to-ts.js";
2
- const toJSONSchema = (spec) => spec["~standard"].jsonSchema.input({ target: "draft-2020-12" });
3
- const readId = (jsonSchema) => jsonSchema === null || jsonSchema === void 0 ? void 0 : jsonSchema.$id;
4
- // This builder prints a node's own description after an em-dash, so drop it
5
- // from the schema before type rendering — otherwise `JSONSchemaToTs` (which
6
- // annotates described nodes inline) would say it twice. Only the ROOT is
7
- // stripped: descriptions nested inside the type still render as inline
8
- // comments, which is the only place they can appear.
9
- const stripRootDescription = (jsonSchema) => jsonSchema !== null && typeof jsonSchema === "object"
10
- ? Object.assign(Object.assign({}, jsonSchema), { description: undefined }) : jsonSchema;
11
- /**
12
- * Render an object schema's properties as an indented, described bullet list:
13
- *
14
- * - count?: number — The number to display
15
- *
16
- * Each field's type comes from `JSONSchemaToTs`; its `description` (when set)
17
- * follows after an em-dash. A non-object schema, or one without properties,
18
- * yields `[]`. Shared by a component's props and a typed event's options — both
19
- * are "object schema → described fields".
20
- */
21
- const describeFields = (jsonSchema, indent) => {
22
- var _a;
23
- const schema = jsonSchema;
24
- if (!(schema === null || schema === void 0 ? void 0 : schema.properties))
25
- return [];
26
- const required = (_a = schema.required) !== null && _a !== void 0 ? _a : [];
27
- return Object.entries(schema.properties).map(([name, field]) => {
1
+ import { isSchemaObject, resolvePointer, specToJSONSchema } from "../json-schema.js";
2
+ import { resolveUrlPolicy, schemaHasUrlFormat } from "../url-policy.js";
3
+ import { dashTail, joinSections, noteSection, stripRootAnnotations, unwrapParens } from "./format.js";
4
+ import { jsonSchemaToTs } from "./json-schema-to-ts.js";
5
+ import { collectSharedTypes } from "./shared-types.js";
6
+ const typeOf = (schema, namedRefs) => jsonSchemaToTs(stripRootAnnotations(schema), { namedRefs });
7
+ const describeFields = (schema, indent, namedRefs) => {
8
+ const required = schema.required ?? [];
9
+ return Object.entries(schema.properties ?? {}).map(([name, field]) => {
28
10
  const optional = required.includes(name) ? "" : "?";
29
- const description = field.description;
30
- return `${indent}- ${name}${optional}: ${JSONSchemaToTs(stripRootDescription(field))}${description ? ` — ${description}` : ""}`;
11
+ // The engine applies defaults before render, so the default is what omitting the field means.
12
+ const fallback = "default" in field ? ` = ${JSON.stringify(field.default)}` : "";
13
+ return `${indent}- ${name}${optional}: ${typeOf(field, namedRefs)}${fallback}${dashTail(field.description)}`;
31
14
  });
32
15
  };
16
+ // The props object whose fields list one per line: the schema itself, or what a root `$ref` names.
17
+ const propsObject = (schema) => {
18
+ if (schema.properties)
19
+ return schema;
20
+ if (typeof schema.$ref !== "string")
21
+ return null;
22
+ const target = resolvePointer(schema.$ref, schema);
23
+ return isSchemaObject(target) && target.properties ? target : null;
24
+ };
25
+ // Any other shape (a union, an intersection, a record) prints as one type, so the model still sees it.
26
+ const describeProps = (schema, namedRefs) => {
27
+ const object = propsObject(schema);
28
+ if (!object)
29
+ return [` Props: ${unwrapParens(typeOf(schema, namedRefs))}`];
30
+ const fields = describeFields(object, " ", namedRefs);
31
+ return fields.length ? [" Props:", ...fields] : [];
32
+ };
33
+ // What a URL prop may hold. A predicate cannot be described, so it prints nothing: say it in `note`.
34
+ const describeUrlProps = (policy) => {
35
+ if (typeof policy === "function")
36
+ return "";
37
+ const { allowRelative, allowSameOrigin, hosts, allowDataImages, origin } = resolveUrlPolicy(policy);
38
+ const allowed = [
39
+ allowRelative && "- relative URL: `/a`, `a/b`, `?q=1`, `#x`",
40
+ allowSameOrigin && `- absolute URL on ${origin ? `\`${origin}\`` : "this site"}`,
41
+ hosts.length > 0 && `- http or https URL on ${hosts.map((host) => `\`${host}\``).join(", ")}`,
42
+ "- `mailto:`, `tel:` or `sms:` link",
43
+ allowDataImages && "- `data:` image, not SVG",
44
+ ].filter(Boolean);
45
+ return `## URL Props\n\nURL-format props (\`format uri\`, \`format uri-reference\`) take only:\n${allowed.join("\n")}\n\nOther URLs fail element.`;
46
+ };
47
+ // A payload with an `id` converts to a root `$ref` into `$defs`; the def's key is the event's name.
48
+ const eventName = (schema) => {
49
+ const name = schema.$ref?.startsWith("#/$defs/") ? schema.$ref.slice("#/$defs/".length) : undefined;
50
+ return name && schema.$defs?.[name] ? name : undefined;
51
+ };
52
+ // A callback payload with an `id` is a common event; two different payloads under one `id` throw.
53
+ const commonEvents = (defs) => {
54
+ const events = new Map();
55
+ for (const def of defs) {
56
+ for (const spec of Object.values(def.callbacks ?? {})) {
57
+ const schema = specToJSONSchema(spec);
58
+ const name = eventName(schema);
59
+ if (!name)
60
+ continue;
61
+ const fingerprint = JSON.stringify(schema);
62
+ const known = events.get(name);
63
+ if (known && known.fingerprint !== fingerprint) {
64
+ throw new Error(`Two callbacks declare the event "${name}" with different payloads — a shared \`id\` must name one shape.`);
65
+ }
66
+ if (!known)
67
+ events.set(name, { schema, fingerprint });
68
+ }
69
+ }
70
+ return new Map([...events].map(([id, { schema }]) => [id, schema]));
71
+ };
33
72
  /**
34
- * Render an array of component defs into the prompt's component section — a
35
- * `# Available Components` names list, an optional `# Common Events` block, then
36
- * `# Component Details`, one entry per visible def: its name + description, a
37
- * `Props:` list (each prop's type + description) and an `Event handlers:` list
38
- * (each handler's signature + description, a typed event's options described one
39
- * level deeper). Types come from `JSONSchemaToTs`; every description rides after
40
- * an em-dash, so nothing the author wrote on a prop, option, or handler is lost.
73
+ * The component menu, from your definitions. A payload with an `id` prints once, under `## Common Events`; a URL
74
+ * prop adds `## URL Props`. Throws on a duplicate name, or on two different payloads with one `id`.
41
75
  *
42
- * `commonEvents` hoists handlers shared across many components (e.g. `onClick`)
43
- * out of every entry. Each passed schema must carry a JSON Schema `$id`; its
44
- * payload is rendered once as a named type under `# Common Events`, and a
45
- * component whose callback is one of them references it as `evt: <$id>` instead
46
- * of re-inlining the payload — killing the per-component duplication. A callback
47
- * is matched by its `$id`, so it stays library-agnostic (anything that emits
48
- * `$id` participates); non-common callbacks render inline as before.
76
+ * @example
77
+ * getComponentsPartialPrompt({ definitions: defs });
49
78
  *
50
- * Host-only defs (`hidden: true`, e.g. RootFragment) are filtered out so the LLM
51
- * never sees host infrastructure in its component menu. Catalog-agnostic: the
52
- * caller passes whatever def set + common events it exposes.
53
- *
54
- * Mirrors `getFunctionsPartialPrompt` — same `# Available X` / `# X Details`
55
- * two-section shape.
56
- *
57
- * Throws on a duplicate component `name`, a common event whose schema has no
58
- * `$id`, or a duplicate common event `$id`.
79
+ * @example
80
+ * getComponentsPartialPrompt({ definitions: [...defs, BadgeDef], urlPolicy: { hosts: ["cdn.example.com"] } });
59
81
  */
60
- export function getComponentsPartialPrompt({ definitions: defs, commonEvents = [], }) {
61
- const seen = new Set();
62
- for (const def of defs) {
63
- if (seen.has(def.name)) {
64
- throw new Error(`Duplicate component name: "${def.name}"`);
65
- }
66
- seen.add(def.name);
67
- }
68
- // Index the common events by `$id`, rendering each once. A missing `$id`
69
- // would leave the handler unmatchable (it would silently inline on every
70
- // component), so it's a hard error — as is a duplicate id, which would make
71
- // the per-component reference ambiguous.
72
- const commonIds = new Set();
73
- const commonLines = [];
74
- for (const schema of commonEvents) {
75
- const jsonSchema = toJSONSchema(schema);
76
- const id = readId(jsonSchema);
77
- if (!id) {
78
- throw new Error('Common event schema is missing a JSON Schema `$id` (add e.g. .meta({ $id: "onClick" }))');
79
- }
80
- if (commonIds.has(id)) {
81
- throw new Error(`Duplicate common event id: "${id}"`);
82
- }
83
- commonIds.add(id);
84
- const ts = JSONSchemaToTs(stripRootDescription(jsonSchema));
85
- const description = jsonSchema.description;
86
- commonLines.push(`- ${id}: ${ts}${description ? ` — ${description}` : ""}`);
82
+ export function getComponentsPartialPrompt({ definitions: defs, urlPolicy, note }) {
83
+ const names = new Set();
84
+ for (const { name } of defs) {
85
+ if (names.has(name))
86
+ throw new Error(`Duplicate component name: "${name}"`);
87
+ names.add(name);
87
88
  }
88
89
  const visible = defs.filter((def) => !def.hidden);
89
- const detail = visible
90
- .map(({ name, description, props, callbacks }) => {
91
- const lines = [`- ${name} — ${description}`];
92
- const propLines = describeFields(toJSONSchema(props), " ");
93
- if (propLines.length)
94
- lines.push(" Props:", ...propLines);
95
- const callbackLines = Object.entries(callbacks || {}).flatMap(([cbName, cbDef]) => {
96
- const cbJSONSchema = toJSONSchema(cbDef);
97
- const id = readId(cbJSONSchema);
98
- if (id && commonIds.has(id)) {
99
- // Common event — its payload is named + described once under
100
- // `# Common Events`; reference it instead of re-inlining here.
101
- return [` - ${cbName}(evt: ${id})`];
102
- }
103
- // The description documents the handler itself, not its `evt`
104
- // argument — append it after the signature, as common events do.
105
- const cbDescription = cbJSONSchema === null || cbJSONSchema === void 0 ? void 0 : cbJSONSchema.description;
106
- const tail = cbDescription ? ` — ${cbDescription}` : "";
107
- // A `null` payload means the handler carries no event data — render it
108
- // as a no-arg call rather than `(evt: null)`.
109
- if ((cbJSONSchema === null || cbJSONSchema === void 0 ? void 0 : cbJSONSchema.type) === "null") {
110
- return [` - ${cbName}()${tail}`];
111
- }
112
- // An object payload's fields are the event's options — list them like
113
- // props, one level deeper. Anything else keeps the inline `evt` type.
114
- const optionLines = describeFields(cbJSONSchema, " ");
115
- if (optionLines.length) {
116
- return [` - ${cbName}(evt)${tail}`, ...optionLines];
117
- }
118
- return [
119
- ` - ${cbName}(evt: ${JSONSchemaToTs(stripRootDescription(cbJSONSchema))})${tail}`,
120
- ];
90
+ // A heading with nothing under it is dropped.
91
+ if (visible.length === 0)
92
+ return noteSection(note);
93
+ // One registry for the block: a `$def` shared by an event payload and a prop prints once.
94
+ const shared = collectSharedTypes();
95
+ const events = commonEvents(visible);
96
+ const eventLines = [...events].flatMap(([name, schema]) => {
97
+ const refs = shared.add(schema);
98
+ const payload = schema.$defs?.[name];
99
+ const tail = dashTail(payload.description);
100
+ const fields = describeFields(payload, " ", refs);
101
+ return fields.length ? [`- ${name}${tail}`, ...fields] : [`- ${name}: ${typeOf(payload, refs)}${tail}`];
102
+ });
103
+ let hasUrlProps = false;
104
+ const details = visible.map(({ name, description, props, callbacks }) => {
105
+ const propsSchema = specToJSONSchema(props);
106
+ hasUrlProps ||= schemaHasUrlFormat(propsSchema);
107
+ const propLines = describeProps(propsSchema, shared.add(propsSchema));
108
+ const handlers = Object.entries(callbacks ?? {}).flatMap(([callback, spec]) => {
109
+ const schema = specToJSONSchema(spec);
110
+ const refs = shared.add(schema);
111
+ const event = eventName(schema);
112
+ if (event && events.has(event))
113
+ return [` - ${callback}(evt: ${event})`];
114
+ // The description documents the handler, not its `evt`.
115
+ const tail = dashTail(schema.description);
116
+ if (schema.type === "null")
117
+ return [` - ${callback}()${tail}`];
118
+ const fields = describeFields(schema, " ", refs);
119
+ if (fields.length)
120
+ return [` - ${callback}(evt)${tail}`, ...fields];
121
+ return [` - ${callback}(evt: ${typeOf(schema, refs)})${tail}`];
121
122
  });
122
- if (callbackLines.length)
123
- lines.push(" Event handlers:", ...callbackLines);
124
- return lines.join("\n");
125
- })
126
- .join("\n\n");
127
- return ("# Available Components\n\n" +
128
- visible.map((def) => def.name).join(", ") +
129
- (commonLines.length
130
- ? `\n\n# Common Events\n\n${commonLines.join("\n")}`
131
- : "") +
132
- "\n\n# Component Details\n\n" +
133
- detail);
123
+ return [
124
+ `- ${name} — ${description}`,
125
+ ...propLines,
126
+ ...(handlers.length ? [" Event handlers:", ...handlers] : []),
127
+ ].join("\n");
128
+ });
129
+ // After the details, so every hoisted definition is in.
130
+ const sharedLines = shared.lines();
131
+ const block = joinSections(`# Available Components\n\n${visible.map((def) => def.name).join(", ")}`, eventLines.length > 0 && `## Common Events\n\n${eventLines.join("\n")}`, `## Component Details\n\n${details.join("\n\n")}`, sharedLines.length > 0 && `## Shared Types\n\n${sharedLines.join("\n")}`, hasUrlProps && describeUrlProps(urlPolicy));
132
+ return joinSections(block.trim(), noteSection(note));
134
133
  }
@@ -1,23 +1,23 @@
1
+ /**
2
+ * Options for `getEditRequestPrompt`.
3
+ *
4
+ * @example
5
+ * const options: EditRequestPromptOptions = { request: "Add a total row.", missingKeys: ["totals"] };
6
+ */
1
7
  export type EditRequestPromptOptions = {
2
- /** The user's change request, e.g. "Add pagination to the orders table". */
8
+ /** The change the user asked for, in their words. */
3
9
  request: string;
4
- /**
5
- * Keys referenced as children in the replayed tree but never emitted —
6
- * typically the tail of an earlier response that was cut off. Listing them
7
- * tells the model those elements do NOT exist, so it re-emits them instead
8
- * of keeping them by reference.
9
- */
10
+ /** Keys named in `children` but never emitted, as after a cut-off response. The model is told to emit them. */
10
11
  missingKeys?: string[];
11
12
  };
12
13
  /**
13
- * The user-turn message for an incremental edit. The host replays the page's
14
- * current JSONL as an assistant turn, then sends this as the next user turn:
15
- * the change request plus the delta convention (re-emit a key to replace its
16
- * subtree — the Partial Replacement rule the system prompt already teaches).
17
- * The model then emits only the elements that change instead of the whole
18
- * page — cheaper and faster, since output tokens dominate both.
14
+ * A user message asking the model to change a page it made, by emitting only the entries that change. One turn's
15
+ * content, not part of the system prompt.
19
16
  *
20
- * Not a system-prompt partial: this is per-turn message content, composed by
21
- * the host into `messages`, not into `system`.
17
+ * @example
18
+ * const messages = [
19
+ * { role: "assistant", content: stored.map((entry) => JSON.stringify(entry)).join("\n") },
20
+ * { role: "user", content: getEditRequestPrompt({ request: "Make the total large." }) },
21
+ * ];
22
22
  */
23
- export declare function getEditRequestPrompt({ request, missingKeys, }: EditRequestPromptOptions): string;
23
+ export declare function getEditRequestPrompt({ request, missingKeys }: EditRequestPromptOptions): string;
@@ -1,22 +1,15 @@
1
+ import { joinSections } from "./format.js";
1
2
  /**
2
- * The user-turn message for an incremental edit. The host replays the page's
3
- * current JSONL as an assistant turn, then sends this as the next user turn:
4
- * the change request plus the delta convention (re-emit a key to replace its
5
- * subtree — the Partial Replacement rule the system prompt already teaches).
6
- * The model then emits only the elements that change instead of the whole
7
- * page — cheaper and faster, since output tokens dominate both.
3
+ * A user message asking the model to change a page it made, by emitting only the entries that change. One turn's
4
+ * content, not part of the system prompt.
8
5
  *
9
- * Not a system-prompt partial: this is per-turn message content, composed by
10
- * the host into `messages`, not into `system`.
6
+ * @example
7
+ * const messages = [
8
+ * { role: "assistant", content: stored.map((entry) => JSON.stringify(entry)).join("\n") },
9
+ * { role: "user", content: getEditRequestPrompt({ request: "Make the total large." }) },
10
+ * ];
11
11
  */
12
- export function getEditRequestPrompt({ request, missingKeys = [], }) {
13
- return [
14
- request,
15
- "Update the previously generated page above. Emit ONLY the elements that change: re-emit an existing key to replace that element (its new `children` array defines the new subtree — children you still reference are kept as-is, so do not re-emit them), use fresh keys for new elements, and do not re-emit unchanged elements. Never re-emit the whole page. `seed` on re-emitted keys does NOT re-run — initialize any NEW state via the `seed` of a newly-keyed element instead.",
16
- missingKeys.length > 0
17
- ? `Warning: the page above references child keys that were never emitted (the earlier response was cut off): ${missingKeys.join(", ")}. These elements do NOT exist — emit them now, or re-emit their parents with corrected children.`
18
- : "",
19
- ]
20
- .filter(Boolean)
21
- .join("\n\n");
12
+ export function getEditRequestPrompt({ request, missingKeys = [] }) {
13
+ return joinSections(request, "Change page above. Emit ONLY entries that change: re-emit key to replace its entry (new `children` define its subtree; listed old children stay, don't re-emit them); new keys for new entries. Never re-emit whole page. Re-emitted `seed` doesn't run again: new state goes in new key's `seed`.", missingKeys.length > 0 &&
14
+ `Warning: page lists keys never emitted (earlier response cut off): ${missingKeys.join(", ")}. They do NOT exist: emit them, or re-emit their parents with fixed \`children\`.`);
22
15
  }
@@ -1,24 +1,35 @@
1
+ import { type EntryErrorReason } from "../entry-error.js";
2
+ /**
3
+ * One failed element, for `getErrorRecoveryPrompt`.
4
+ *
5
+ * @example
6
+ * const failure: RenderFailure = { key: "chart", message: error.message, reason: error.reason };
7
+ */
1
8
  export type RenderFailure = {
2
- /** The `key` of the element whose render failed. */
9
+ /** The failed element's key. */
3
10
  key: string;
4
- /** The runtime error message, verbatim. */
11
+ /** What went wrong, e.g. the error's `message`. */
5
12
  message: string;
13
+ /** Adds what this kind of failure means to the line; a reason whose fault is `"environment"` adds nothing. */
14
+ reason?: EntryErrorReason;
6
15
  };
16
+ /**
17
+ * Options for `getErrorRecoveryPrompt`.
18
+ *
19
+ * @example
20
+ * const options: ErrorRecoveryPromptOptions = { failures: [{ key: "chart", message: error.message }] };
21
+ */
7
22
  export type ErrorRecoveryPromptOptions = {
8
- /** The failures to report, one per failed element. */
23
+ /** One per failed element. */
9
24
  failures: RenderFailure[];
10
25
  };
11
26
  /**
12
- * The user-turn message for user-triggered error recovery: an element failed
13
- * at runtime (its error slot rendered), and the host asks the model to re-emit
14
- * it corrected. Deliberately surface-neutral — it states the failures and the
15
- * fix request, while the output conventions come from the surface's own
16
- * prompt: on a page surface the host passes this through its edit pipeline
17
- * (replayed document + `getEditRequestPrompt`, which carries the delta
18
- * convention), on a chat surface it goes out as a plain user message and the
19
- * model replies with a corrected fence.
27
+ * A user message naming the elements that failed, so the model sends fixed ones under the same keys. One turn's
28
+ * content, not part of the system prompt.
20
29
  *
21
- * Not a system-prompt partial: this is per-turn message content, composed by
22
- * the host into `messages`, not into `system`.
30
+ * @example
31
+ * getErrorRecoveryPrompt({
32
+ * failures: [{ key: "chart", message: "chartData.map is not a function", reason: "expression-runtime" }],
33
+ * });
23
34
  */
24
- export declare function getErrorRecoveryPrompt({ failures, }: ErrorRecoveryPromptOptions): string;
35
+ export declare function getErrorRecoveryPrompt({ failures }: ErrorRecoveryPromptOptions): string;
@@ -1,23 +1,26 @@
1
+ import { FAULT_BY_REASON, REASON_DESCRIPTIONS } from "../entry-error.js";
1
2
  /**
2
- * The user-turn message for user-triggered error recovery: an element failed
3
- * at runtime (its error slot rendered), and the host asks the model to re-emit
4
- * it corrected. Deliberately surface-neutral — it states the failures and the
5
- * fix request, while the output conventions come from the surface's own
6
- * prompt: on a page surface the host passes this through its edit pipeline
7
- * (replayed document + `getEditRequestPrompt`, which carries the delta
8
- * convention), on a chat surface it goes out as a plain user message and the
9
- * model replies with a corrected fence.
3
+ * A user message naming the elements that failed, so the model sends fixed ones under the same keys. One turn's
4
+ * content, not part of the system prompt.
10
5
  *
11
- * Not a system-prompt partial: this is per-turn message content, composed by
12
- * the host into `messages`, not into `system`.
6
+ * @example
7
+ * getErrorRecoveryPrompt({
8
+ * failures: [{ key: "chart", message: "chartData.map is not a function", reason: "expression-runtime" }],
9
+ * });
13
10
  */
14
- export function getErrorRecoveryPrompt({ failures, }) {
11
+ export function getErrorRecoveryPrompt({ failures }) {
15
12
  const list = failures
16
- .map((failure) => `- Element \`${failure.key}\`: ${failure.message}`)
13
+ .map(({ key, message, reason }) => {
14
+ const line = `- Entry \`${key}\`: ${message}`;
15
+ // Environment faults are not the model's to fix.
16
+ if (!reason || FAULT_BY_REASON[reason] === "environment")
17
+ return line;
18
+ return `${line} (${reason} — ${REASON_DESCRIPTIONS[reason]})`;
19
+ })
17
20
  .join("\n");
18
21
  return [
19
- "The generated UI hit runtime errors:",
22
+ "Rendered UI has errors:",
20
23
  list,
21
- "Diagnose each failure and emit a corrected version of the failed element, keeping its `key`. Fix the cause, not just the reported line — if the error comes from state or an expression that another element sets up (e.g. a `seed` elsewhere), correct that element as well.",
24
+ "Re-emit each failed entry fixed, same `key`. Fix cause: when it comes from another entry (like `seed` elsewhere), fix that entry too.",
22
25
  ].join("\n\n");
23
26
  }
@@ -1,24 +1,23 @@
1
- /** No options yet — the type is reserved so the signature can grow without breaking callers. */
2
- export type ExpressionsPromptOptions = Record<string, never>;
3
1
  /**
4
- * The `# JavaScript Expressions` block — the micro-expression syntax contract
5
- * the generation LLM follows when authoring `expr` values (props, hidden,
6
- * seed, callbacks). Catalog-/app-agnostic: it documents only the evaluator
7
- * surface (allowed syntax, the `scopes` / `evt` context variables), not any
8
- * specific host functions.
2
+ * Options for `getExpressionsPartialPrompt`.
9
3
  *
10
- * Authored in `md/EXPRESSIONS.md` and imported as a JSON string via the
11
- * md-to-json pipeline (`npm run md-to-json` regenerates the `.json` sibling
12
- * after edits). The doc carries a visible `🔴ALLOWED_GLOBALS🔴` slot that this
13
- * builder fills from `globals.ts` (the single source of truth for the sandbox
14
- * allow-list) so the prompt can never drift from what the evaluator actually
15
- * permits.
4
+ * @example
5
+ * const options: ExpressionsPromptOptions = { maxLength: 500 };
6
+ */
7
+ export type ExpressionsPromptOptions = {
8
+ /** The evaluator's `maxSourceLength`: the longest expression, in characters. Default 1000. */
9
+ maxLength?: number;
10
+ /** Your text, appended verbatim as a trailing `## Note`. */
11
+ note?: string;
12
+ };
13
+ /**
14
+ * The `# JavaScript Expressions` block: the language's rules, its globals and a few idioms. The globals and the
15
+ * default length come from `@uicast/expr` itself.
16
+ *
17
+ * @example
18
+ * getExpressionsPartialPrompt();
16
19
  *
17
- * A partial-prompt primitive: the consuming app's prompt assembler composes
18
- * this — alongside the other `get*PartialPrompt` builders — into the full
19
- * system prompt. The closing line refers to host functions "listed below",
20
- * so the app should join its function-list partial (e.g.
21
- * `getFunctionsPartialPrompt({ functions })`) as a later section. core
22
- * ships the pieces; the app owns the assembly.
20
+ * @example
21
+ * getExpressionsPartialPrompt({ maxLength: 500 }); // with new Evaluator({ maxSourceLength: 500 })
23
22
  */
24
- export declare function getExpressionsPartialPrompt(_options?: ExpressionsPromptOptions): string;
23
+ export declare function getExpressionsPartialPrompt({ maxLength, note, }?: ExpressionsPromptOptions): string;
@@ -1,27 +1,17 @@
1
- import { ALLOWED_GLOBALS } from "../expr/globals.js";
1
+ import { ALLOWED_GLOBALS, DEFAULT_MAX_SOURCE_LENGTH } from "@uicast/expr/internal";
2
+ import { joinSections, noteSection } from "./format.js";
2
3
  import EXPRESSIONS from "./md/EXPRESSIONS.json" with { type: "json" };
3
4
  /**
4
- * The `# JavaScript Expressions` block — the micro-expression syntax contract
5
- * the generation LLM follows when authoring `expr` values (props, hidden,
6
- * seed, callbacks). Catalog-/app-agnostic: it documents only the evaluator
7
- * surface (allowed syntax, the `scopes` / `evt` context variables), not any
8
- * specific host functions.
5
+ * The `# JavaScript Expressions` block: the language's rules, its globals and a few idioms. The globals and the
6
+ * default length come from `@uicast/expr` itself.
9
7
  *
10
- * Authored in `md/EXPRESSIONS.md` and imported as a JSON string via the
11
- * md-to-json pipeline (`npm run md-to-json` regenerates the `.json` sibling
12
- * after edits). The doc carries a visible `🔴ALLOWED_GLOBALS🔴` slot that this
13
- * builder fills from `globals.ts` (the single source of truth for the sandbox
14
- * allow-list) so the prompt can never drift from what the evaluator actually
15
- * permits.
8
+ * @example
9
+ * getExpressionsPartialPrompt();
16
10
  *
17
- * A partial-prompt primitive: the consuming app's prompt assembler composes
18
- * this — alongside the other `get*PartialPrompt` builders — into the full
19
- * system prompt. The closing line refers to host functions "listed below",
20
- * so the app should join its function-list partial (e.g.
21
- * `getFunctionsPartialPrompt({ functions })`) as a later section. core
22
- * ships the pieces; the app owns the assembly.
11
+ * @example
12
+ * getExpressionsPartialPrompt({ maxLength: 500 }); // with new Evaluator({ maxSourceLength: 500 })
23
13
  */
24
- export function getExpressionsPartialPrompt(_options = {}) {
25
- const globals = ALLOWED_GLOBALS.join(", ");
26
- return EXPRESSIONS.replace("🔴ALLOWED_GLOBALS🔴", globals);
14
+ export function getExpressionsPartialPrompt({ maxLength = DEFAULT_MAX_SOURCE_LENGTH, note, } = {}) {
15
+ const language = EXPRESSIONS.replace("🔴ALLOWED_GLOBALS🔴", ALLOWED_GLOBALS.join(", ")).replace("🔴MAX_LENGTH🔴", String(maxLength));
16
+ return joinSections(language.trim(), noteSection(note));
27
17
  }
@@ -1,22 +1,22 @@
1
- import type { StandardToolV0 } from "standard-tool";
1
+ import type { StandardToolV0 } from "@uicast/expr";
2
+ /**
3
+ * Options for `getFunctionsPartialPrompt`.
4
+ *
5
+ * @example
6
+ * const options: FunctionsPromptOptions = { functions: tools, note: "Amounts are in cents." };
7
+ */
2
8
  export type FunctionsPromptOptions = {
3
- /** The host functions to advertise — the same set handed to the renderer's `functions` prop. */
9
+ /** Your host functions: the array the evaluator binds. An empty one adds nothing. */
4
10
  functions: StandardToolV0[];
11
+ /** Your text, appended verbatim as this section's trailing `## Note`. */
12
+ note?: string;
5
13
  };
6
14
  /**
7
- * Render an array of `StandardToolV0`s into the prompt's function section — a
8
- * `# Available Functions` names list followed by `# Function Details`, one
9
- * Markdown bullet per tool as a TypeScript-style call signature derived from
10
- * the tool's Zod schemas:
11
- *
12
- * - updateRows({ sheet: string, ... }) => void: <description>
13
- *
14
- * A tool with no `inputSchema` renders `name()`; one with no `outputSchema`
15
- * renders `=> void`.
15
+ * The `# Available Functions` block: a call signature per host function, from its schemas. A tool with no
16
+ * `outputSchema` prints `=> unknown`: undeclared, not empty. Throws on a duplicate name, a name that is not an
17
+ * identifier, or a reserved one: `scopes`, `evt`, `currentValue` or a global such as `Math`.
16
18
  *
17
- * Catalog-agnostic: a caller passes whatever tool set it exposes to the LLM
18
- * (whatever host functions it exposes) and composes the returned block into the
19
- * surrounding prompt it needs (extra calling conventions, examples, …).
20
- * Mirrors `getComponentsPartialPrompt` — same two-section shape.
19
+ * @example
20
+ * getFunctionsPartialPrompt({ functions: tools }); // the same array as new Evaluator({ functions: tools })
21
21
  */
22
- export declare function getFunctionsPartialPrompt({ functions: tools, }: FunctionsPromptOptions): string;
22
+ export declare function getFunctionsPartialPrompt({ functions, note }: FunctionsPromptOptions): string;