@uicast/core 0.0.1-beta.2 → 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/CHANGELOG.md +3 -13
  2. package/README.md +120 -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,62 +1,52 @@
1
1
  import { EntryError } from "../entry-error.js";
2
- import { ALLOWED_GLOBALS } from "./globals.js";
3
- import { SaferEval, SaferEvalError } from "./safer-eval.js";
4
- const saferEval = new SaferEval({
5
- allowGlobals: ALLOWED_GLOBALS,
6
- enforceAllowlist: true,
7
- });
8
- // Every scopes.X.Y path an expression reads, via the shared SaferEval singleton
9
- // (so extractDeps doesn't reach into the evaluator). Same parse cache as eval().
10
- export const getScopeReads = (expr) => saferEval.scopeReads(expr);
11
- // Classify an evaluation failure by provenance: SaferEval's own rejections
12
- // carry their reason (syntax / policy / unknown identifier); an EntryError
13
- // passes through (a host function already tagged it); anything else threw
14
- // while the compiled expression ran.
15
- const wrapEvalError = (err) => {
2
+ import { CONTEXT_NAMES } from "../constants.js";
3
+ import { unwrapRows } from "../scope/create-proxy-scope.js";
4
+ // Identifier validity is the evaluator's own check; this screens only uicast's names.
5
+ const screened = new WeakSet();
6
+ const screen = (evaluator) => {
7
+ if (screened.has(evaluator))
8
+ return;
9
+ for (const { name } of evaluator.functions) {
10
+ if (CONTEXT_NAMES.has(name)) {
11
+ throw new EntryError(`Host function name "${name}" is reserved — it would shadow the expression context of the same name`, { reason: "host-function" });
12
+ }
13
+ }
14
+ screened.add(evaluator);
15
+ };
16
+ // Brand check: core has no runtime import of @uicast/expr.
17
+ const isExpressionError = (err) => typeof err === "object" &&
18
+ err !== null &&
19
+ err.uicastExpressionError === true;
20
+ export const wrapEvalError = (err) => {
16
21
  if (EntryError.is(err))
17
22
  return err;
18
- if (err instanceof SaferEvalError)
23
+ if (isExpressionError(err)) {
24
+ // A host that classified its own failure keeps that verdict.
25
+ if (EntryError.is(err.cause))
26
+ return err.cause;
19
27
  return EntryError.wrap(err, err.reason);
28
+ }
20
29
  return EntryError.wrap(err, "expression-runtime");
21
30
  };
22
- // Evaluate a ValueSource: a `literal` is returned as-is, an `expr` runs through
23
- // SaferEval against context + host functions. Returns unknown — an async expr
24
- // resolves to a Promise (callers check `value instanceof Promise`). `functions`
25
- // (a StandardToolV0[]) are spread after context, so a host fn wins a name clash.
26
- // Every failure surfaces as a classified EntryError.
27
- export const evaluate = (expr, context, options) => {
28
- var _a;
31
+ export const evaluate = (expr, context, evaluator) => {
29
32
  if ("literal" in expr)
30
33
  return expr.literal;
31
- if (!expr.expr)
34
+ // An empty string falls through to the evaluator, which rejects it.
35
+ if (expr.expr == null)
32
36
  return null;
33
- const functions = Object.fromEntries(((_a = options === null || options === void 0 ? void 0 : options.functions) !== null && _a !== void 0 ? _a : []).map((tool) => [
34
- tool.name,
35
- // This closure is the engine/host boundary: anything escaping
36
- // `tool.execute` is host code failing — unless the host already threw a
37
- // classified EntryError itself (e.g. "invalid-arguments"), which wrap()
38
- // passes through.
39
- (input) => {
40
- try {
41
- const result = tool.execute(input);
42
- return result instanceof Promise
43
- ? result.catch((err) => {
44
- throw EntryError.wrap(err, "host-function");
45
- })
46
- : result;
47
- }
48
- catch (err) {
49
- throw EntryError.wrap(err, "host-function");
50
- }
51
- },
52
- ]));
53
37
  try {
54
- const result = saferEval.eval(expr.expr, Object.assign(Object.assign({}, context), functions), options === null || options === void 0 ? void 0 : options.allowedGlobals);
38
+ screen(evaluator);
39
+ const source = expr.expr;
40
+ const result = evaluator.eval(source, context);
41
+ // Only a read of a whole scope (`scopes.row`) can put a row window in the result.
42
+ const data = (value) => evaluator.memberReads(source, "scopes").some((read) => read.indexOf(".", "scopes.".length) === -1)
43
+ ? unwrapRows(value)
44
+ : value;
55
45
  return result instanceof Promise
56
- ? result.catch((err) => {
46
+ ? result.then(data, (err) => {
57
47
  throw wrapEvalError(err);
58
48
  })
59
- : result;
49
+ : data(result);
60
50
  }
61
51
  catch (err) {
62
52
  throw wrapEvalError(err);
@@ -1,2 +1,4 @@
1
+ import type { ExpressionEvaluator } from "@uicast/expr";
1
2
  import { type ComponentEntry } from "../types.js";
2
- export declare function extractDeps(entry: ComponentEntry): string[];
3
+ export type DepsPart = "all" | "render" | "each";
4
+ export declare function extractDeps(entry: ComponentEntry, evaluator: ExpressionEvaluator, part: DepsPart): string[];
@@ -1,30 +1,30 @@
1
1
  import { isComponentListEntry } from "../types.js";
2
- import { getScopeReads } from "./evaluate.js";
3
- // Entries are immutable, so cached reads never go stale; WeakMap so a dropped
4
- // entry can be collected.
2
+ import { depKey } from "../scope/parse-scope.js";
3
+ // Entries are immutable, so cached reads never go stale.
5
4
  const cache = new WeakMap();
6
- // The reactive scopes.X.Y paths an entry reads, across its props, hidden, and each
7
- // expressions — the renderer subscribes to these. (seed run once and
8
- // callbacks read at fire time, so neither is scanned.)
9
- export function extractDeps(entry) {
10
- const cached = cache.get(entry);
5
+ // seed runs once and callbacks read at fire time, so neither is scanned.
6
+ export function extractDeps(entry, evaluator, part) {
7
+ const slots = cache.get(entry);
8
+ const cached = slots?.[part];
11
9
  if (cached)
12
10
  return cached;
13
11
  const out = new Set();
14
- if (entry.props && "expr" in entry.props && entry.props.expr) {
15
- for (const r of getScopeReads(entry.props.expr))
16
- out.add(r);
17
- }
18
- if (entry.hidden) {
19
- for (const r of getScopeReads(entry.hidden))
20
- out.add(r);
12
+ const add = (expr) => {
13
+ for (const r of evaluator.memberReads(expr, "scopes"))
14
+ out.add(depKey(r));
15
+ };
16
+ if (part !== "each") {
17
+ if (entry.props && "expr" in entry.props && entry.props.expr)
18
+ add(entry.props.expr);
19
+ if (entry.hidden)
20
+ add(entry.hidden);
21
+ if (entry.loading)
22
+ add(entry.loading);
21
23
  }
22
24
  // `each` reads both the list and anything its filter touches (e.g. a search term).
23
- if (isComponentListEntry(entry)) {
24
- for (const r of getScopeReads(entry.each))
25
- out.add(r);
26
- }
25
+ if (part !== "render" && isComponentListEntry(entry))
26
+ add(entry.each);
27
27
  const result = [...out];
28
- cache.set(entry, result);
28
+ cache.set(entry, { ...slots, [part]: result });
29
29
  return result;
30
30
  }
@@ -0,0 +1,8 @@
1
+ import type { ExpressionEvaluator } from "@uicast/expr";
2
+ type PlannableStep = {
3
+ set?: string;
4
+ expr?: string;
5
+ confirm?: string;
6
+ };
7
+ export declare function planStepWaves<T extends PlannableStep>(steps: readonly T[], evaluator: ExpressionEvaluator, isBarrier?: (step: T) => boolean, declaredWrites?: (step: T) => string[]): T[][];
8
+ export {};
@@ -0,0 +1,45 @@
1
+ import { depKey } from "../scope/parse-scope.js";
2
+ import { wrapEvalError } from "./evaluate.js";
3
+ // Reads wait for earlier writes, independent steps share a wave, `confirm` and `isBarrier` steps are barriers.
4
+ export function planStepWaves(steps, evaluator, isBarrier, declaredWrites) {
5
+ const waves = [];
6
+ let wave = [];
7
+ let waveWrites = new Set();
8
+ const close = () => {
9
+ if (wave.length)
10
+ waves.push(wave);
11
+ wave = [];
12
+ waveWrites = new Set();
13
+ };
14
+ for (const step of steps) {
15
+ const reads = [];
16
+ if (step.expr) {
17
+ // Planning parses before `evaluate` runs, so a bad expression must be classified here.
18
+ try {
19
+ for (const r of evaluator.memberReads(step.expr, "scopes"))
20
+ reads.push(depKey(r));
21
+ // `currentValue` is the value at the step's own `set`.
22
+ if (step.set && evaluator.validate(step.expr).freeIds.includes("currentValue"))
23
+ reads.push(step.set);
24
+ }
25
+ catch (err) {
26
+ throw wrapEvalError(err);
27
+ }
28
+ }
29
+ // A whole-scope read (`scopes.root.*`) waits for any write into that scope.
30
+ const dependsOnWave = reads.some((r) => r.endsWith(".*") ? [...waveWrites].some((w) => w.startsWith(r.slice(0, -1))) : waveWrites.has(r));
31
+ const barrier = !!step.confirm || !!isBarrier?.(step);
32
+ if (dependsOnWave || barrier)
33
+ close();
34
+ wave.push(step);
35
+ if (step.set)
36
+ waveWrites.add(step.set);
37
+ if (declaredWrites)
38
+ for (const w of declaredWrites(step))
39
+ waveWrites.add(w);
40
+ if (barrier)
41
+ close();
42
+ }
43
+ close();
44
+ return waves;
45
+ }
package/dist/index.d.ts CHANGED
@@ -1,9 +1,8 @@
1
- export { createComponentDefinition } from "./def/create-component-definition.js";
2
- export { evaluate } from "./expr/evaluate.js";
3
- export { extractDeps } from "./expr/extract-deps.js";
4
- export { EntryError, type EntryErrorReason, type EntryFault, } from "./entry-error.js";
1
+ export type { ExpressionEvaluator } from "@uicast/expr";
2
+ export { buildElementsByKey } from "./build-elements-by-key.js";
3
+ export { createComponentDefinition } from "./create-component-definition.js";
4
+ export { EntryError, type EntryErrorReason, type EntryFault } from "./entry-error.js";
5
5
  export { createProxyScope, type ReactiveProxy } from "./scope/create-proxy-scope.js";
6
- export { parseScope } from "./scope/parse-scope.js";
7
- export { buildElementsById } from "./utils/utils.js";
8
- export { streamJsonLines } from "./stream/stream-json-lines.js";
9
- export { type ComponentEntry, type ComponentListEntry, type ComponentDefinition, type CombinedSpec, isComponentEntry, isComponentListEntry, } from "./types.js";
6
+ export { streamJsonLines } from "./stream-json-lines.js";
7
+ export { type CallbackValueSourceAssignment, type CombinedSpec, type ComponentDefinition, type ComponentEntry, type ComponentListEntry, isComponentEntry, type ValueSource, type ValueSourceAssignment, } from "./types.js";
8
+ export type { UrlPolicy } from "./url-policy.js";
package/dist/index.js CHANGED
@@ -1,10 +1,6 @@
1
- // uicast public API. Prompt-partial builders live in uicast/prompt.
2
- export { createComponentDefinition } from "./def/create-component-definition.js";
3
- export { evaluate } from "./expr/evaluate.js";
4
- export { extractDeps } from "./expr/extract-deps.js";
5
- export { EntryError, } from "./entry-error.js";
1
+ export { buildElementsByKey } from "./build-elements-by-key.js";
2
+ export { createComponentDefinition } from "./create-component-definition.js";
3
+ export { EntryError } from "./entry-error.js";
6
4
  export { createProxyScope } from "./scope/create-proxy-scope.js";
7
- export { parseScope } from "./scope/parse-scope.js";
8
- export { buildElementsById } from "./utils/utils.js";
9
- export { streamJsonLines } from "./stream/stream-json-lines.js";
10
- export { isComponentEntry, isComponentListEntry, } from "./types.js";
5
+ export { streamJsonLines } from "./stream-json-lines.js";
6
+ export { isComponentEntry, } from "./types.js";
@@ -0,0 +1,12 @@
1
+ export { CALLBACK_DEBOUNCE_MS } from "./constants.js";
2
+ export { entryShapeError } from "./entry-shape.js";
3
+ export { evaluate } from "./expr/evaluate.js";
4
+ export { type DepsPart, extractDeps } from "./expr/extract-deps.js";
5
+ export { planStepWaves } from "./expr/plan-step-waves.js";
6
+ export { type JSONSchema, specToJSONSchema } from "./json-schema.js";
7
+ export { noteSection } from "./prompt/format.js";
8
+ export { countEmits, createRowScope, createRowState, getForwardTargets, type RowScope, type RowState, } from "./scope/create-proxy-scope.js";
9
+ export { parseScope } from "./scope/parse-scope.js";
10
+ export { entrySetAddressError, parseSetAddress } from "./scope/parse-set-address.js";
11
+ export { isComponentListEntry } from "./types.js";
12
+ export { findUrlViolations, schemaHasUrlFormat } from "./url-policy.js";
@@ -0,0 +1,13 @@
1
+ // No semver guarantee.
2
+ export { CALLBACK_DEBOUNCE_MS } from "./constants.js";
3
+ export { entryShapeError } from "./entry-shape.js";
4
+ export { evaluate } from "./expr/evaluate.js";
5
+ export { extractDeps } from "./expr/extract-deps.js";
6
+ export { planStepWaves } from "./expr/plan-step-waves.js";
7
+ export { specToJSONSchema } from "./json-schema.js";
8
+ export { noteSection } from "./prompt/format.js";
9
+ export { countEmits, createRowScope, createRowState, getForwardTargets, } from "./scope/create-proxy-scope.js";
10
+ export { parseScope } from "./scope/parse-scope.js";
11
+ export { entrySetAddressError, parseSetAddress } from "./scope/parse-set-address.js";
12
+ export { isComponentListEntry } from "./types.js";
13
+ export { findUrlViolations, schemaHasUrlFormat } from "./url-policy.js";
@@ -0,0 +1,58 @@
1
+ import type { CombinedSpec } from "./types.js";
2
+ type JSONSchemaType = "object" | "array" | "string" | "number" | "boolean" | "null" | "integer";
3
+ export interface JSONSchema {
4
+ type?: JSONSchemaType | JSONSchemaType[];
5
+ format?: string;
6
+ pattern?: string;
7
+ $ref?: string;
8
+ items?: boolean | JSONSchema;
9
+ prefixItems?: JSONSchema[];
10
+ enum?: unknown[];
11
+ minimum?: number;
12
+ maximum?: number;
13
+ exclusiveMinimum?: number;
14
+ exclusiveMaximum?: number;
15
+ multipleOf?: number;
16
+ minItems?: number;
17
+ maxItems?: number;
18
+ uniqueItems?: boolean;
19
+ default?: unknown;
20
+ $id?: string;
21
+ description?: string;
22
+ properties?: {
23
+ [key: string]: JSONSchema;
24
+ };
25
+ patternProperties?: {
26
+ [pattern: string]: JSONSchema;
27
+ };
28
+ propertyNames?: JSONSchema;
29
+ required?: string[];
30
+ not?: JSONSchema;
31
+ $defs?: {
32
+ [key: string]: JSONSchema;
33
+ };
34
+ definitions?: {
35
+ [key: string]: JSONSchema;
36
+ };
37
+ additionalProperties?: boolean | JSONSchema;
38
+ unevaluatedProperties?: boolean | JSONSchema;
39
+ unevaluatedItems?: boolean | JSONSchema;
40
+ contains?: JSONSchema;
41
+ dependentSchemas?: {
42
+ [key: string]: JSONSchema;
43
+ };
44
+ if?: JSONSchema;
45
+ then?: JSONSchema;
46
+ else?: JSONSchema;
47
+ anyOf?: JSONSchema[];
48
+ oneOf?: JSONSchema[];
49
+ allOf?: JSONSchema[];
50
+ const?: unknown;
51
+ minLength?: number;
52
+ maxLength?: number;
53
+ }
54
+ export declare const isSchemaObject: (node: unknown) => node is JSONSchema;
55
+ export declare const resolvePointer: (ref: string, root: unknown) => unknown;
56
+ export declare const collectRefs: (node: unknown, out?: Set<string>) => Set<string>;
57
+ export declare const specToJSONSchema: (spec: CombinedSpec) => JSONSchema;
58
+ export {};
@@ -0,0 +1,73 @@
1
+ export const isSchemaObject = (node) => typeof node === "object" && node !== null && !Array.isArray(node);
2
+ // `#` is the document itself. Own keys only, so a pointer never lands on a prototype member.
3
+ export const resolvePointer = (ref, root) => {
4
+ if (ref === "#")
5
+ return root;
6
+ if (!ref.startsWith("#/"))
7
+ return undefined;
8
+ let node = root;
9
+ for (const segment of ref.slice(2).split("/")) {
10
+ const key = segment.replace(/~1/g, "/").replace(/~0/g, "~");
11
+ if (typeof node !== "object" || node === null || !Object.hasOwn(node, key))
12
+ return undefined;
13
+ node = Reflect.get(node, key);
14
+ }
15
+ return node;
16
+ };
17
+ // Every `$ref` string at or under `node`.
18
+ export const collectRefs = (node, out = new Set()) => {
19
+ if (Array.isArray(node)) {
20
+ for (const item of node)
21
+ collectRefs(item, out);
22
+ }
23
+ else if (node !== null && typeof node === "object") {
24
+ for (const [key, value] of Object.entries(node)) {
25
+ if (key === "$ref" && typeof value === "string")
26
+ out.add(value);
27
+ else
28
+ collectRefs(value, out);
29
+ }
30
+ }
31
+ return out;
32
+ };
33
+ const DEFS = "#/$defs/";
34
+ const renameRefs = (node, renamed) => {
35
+ if (Array.isArray(node))
36
+ return node.map((item) => renameRefs(item, renamed));
37
+ if (node === null || typeof node !== "object")
38
+ return node;
39
+ return Object.fromEntries(Object.entries(node).map(([key, value]) => [
40
+ key,
41
+ key === "$ref" && typeof value === "string" ? (renamed.get(value) ?? value) : renameRefs(value, renamed),
42
+ ]));
43
+ };
44
+ // A def that is only a `$ref` is an alias. When nothing else uses its target, both are one type under the alias's name.
45
+ // zod emits such a pair for a recursive schema given a new description.
46
+ const foldAliasDefs = (schema) => {
47
+ const defs = schema.$defs;
48
+ if (!defs)
49
+ return schema;
50
+ const targetOf = (node) => {
51
+ const ref = node?.$ref;
52
+ return typeof ref === "string" && ref.startsWith(DEFS) && ref.slice(DEFS.length) in defs ? ref : undefined;
53
+ };
54
+ const used = collectRefs({ ...schema, $defs: undefined });
55
+ for (const node of Object.values(defs))
56
+ if (!targetOf(node))
57
+ collectRefs(node, used);
58
+ const renamed = new Map();
59
+ const merged = { ...defs };
60
+ for (const [key, node] of Object.entries(defs)) {
61
+ const target = targetOf(node);
62
+ if (!target || used.has(target) || renamed.has(target))
63
+ continue;
64
+ const { $ref, ...annotations } = node;
65
+ merged[key] = { ...defs[target.slice(DEFS.length)], ...annotations };
66
+ renamed.set(target, DEFS + key);
67
+ }
68
+ if (renamed.size === 0)
69
+ return schema;
70
+ const kept = Object.fromEntries(Object.entries(merged).filter(([key]) => !renamed.has(DEFS + key)));
71
+ return renameRefs({ ...schema, $defs: kept }, renamed);
72
+ };
73
+ export const specToJSONSchema = (spec) => foldAliasDefs(spec["~standard"].jsonSchema.input({ target: "draft-2020-12" }));
@@ -0,0 +1,5 @@
1
+ export declare const dashTail: (description: string | undefined) => string;
2
+ export declare const unwrapParens: (ts: string) => string;
3
+ export declare const stripRootAnnotations: (jsonSchema: unknown) => unknown;
4
+ export declare const noteSection: (note: string | undefined) => string;
5
+ export declare const joinSections: (...sections: (string | false | null | undefined)[]) => string;
@@ -0,0 +1,22 @@
1
+ // The prompt's convention: a type, then ` — description`.
2
+ export const dashTail = (description) => (description ? ` — ${description}` : "");
3
+ // Drops one pair of parentheses around the whole type: `(A & B)` → `A & B`, but `(A) | (B)` stays.
4
+ export const unwrapParens = (ts) => {
5
+ if (!ts.startsWith("(") || !ts.endsWith(")"))
6
+ return ts;
7
+ let depth = 0;
8
+ for (let i = 0; i < ts.length - 1; i++) {
9
+ if (ts[i] === "(")
10
+ depth++;
11
+ else if (ts[i] === ")" && --depth === 0)
12
+ return ts;
13
+ }
14
+ return ts.slice(1, -1);
15
+ };
16
+ // The builder prints the root description and default itself.
17
+ export const stripRootAnnotations = (jsonSchema) => jsonSchema !== null && typeof jsonSchema === "object"
18
+ ? { ...jsonSchema, description: undefined, default: undefined }
19
+ : jsonSchema;
20
+ export const noteSection = (note) => (note?.trim() ? `## Note\n\n${note.trim()}` : "");
21
+ // A partial's sections, blank-line separated; empty ones drop out.
22
+ export const joinSections = (...sections) => sections.filter(Boolean).join("\n\n");
@@ -1,13 +1,23 @@
1
- /** No options yet — the type is reserved so the signature can grow without breaking callers. */
2
- export type CommonInstructionsPromptOptions = Record<string, never>;
3
1
  /**
4
- * The LLM-facing instruction block — the contract authored in
5
- * `md/INSTRUCTIONS.md` and imported here as a JSON string via the md-to-json
6
- * pipeline (`npm run md-to-json` regenerates the `.json` sibling after edits).
2
+ * Options for `getCommonInstructionsPartialPrompt`.
7
3
  *
8
- * A partial-prompt primitive: the consuming app's prompt assembler composes
9
- * this — alongside the other `get*PartialPrompt` builders — into the full
10
- * system prompt it sends to the generation LLM. core ships the pieces; the app
11
- * owns the assembly.
4
+ * @example
5
+ * const options: CommonInstructionsPromptOptions = { maxListItems: 50 };
12
6
  */
13
- export declare function getCommonInstructionsPartialPrompt(_options?: CommonInstructionsPromptOptions): string;
7
+ export type CommonInstructionsPromptOptions = {
8
+ /** Most items one list may render; the model pages or filters past it. Default 100. */
9
+ maxListItems?: number;
10
+ /** Your text, appended verbatim as a trailing `## Note` section. */
11
+ note?: string;
12
+ };
13
+ /**
14
+ * The rules every surface needs: the output format, partial replacement, and the names an expression can read
15
+ * (`scopes`, `evt`, `currentValue`, host functions). The language itself is `getExpressionsPartialPrompt`; use both.
16
+ *
17
+ * @example
18
+ * const system = [getCommonInstructionsPartialPrompt(), getExpressionsPartialPrompt()].join("\n\n");
19
+ *
20
+ * @example
21
+ * getCommonInstructionsPartialPrompt({ note: "scopes.userCtx holds the signed-in user: name, plan; read-only." });
22
+ */
23
+ export declare function getCommonInstructionsPartialPrompt({ maxListItems, note, }?: CommonInstructionsPromptOptions): string;
@@ -1,14 +1,16 @@
1
+ import { joinSections, noteSection } from "./format.js";
2
+ // Generated from `md/INSTRUCTIONS.md`: run `npm run md-to-json` after editing it.
1
3
  import INSTRUCTIONS from "./md/INSTRUCTIONS.json" with { type: "json" };
2
4
  /**
3
- * The LLM-facing instruction block — the contract authored in
4
- * `md/INSTRUCTIONS.md` and imported here as a JSON string via the md-to-json
5
- * pipeline (`npm run md-to-json` regenerates the `.json` sibling after edits).
5
+ * The rules every surface needs: the output format, partial replacement, and the names an expression can read
6
+ * (`scopes`, `evt`, `currentValue`, host functions). The language itself is `getExpressionsPartialPrompt`; use both.
6
7
  *
7
- * A partial-prompt primitive: the consuming app's prompt assembler composes
8
- * this — alongside the other `get*PartialPrompt` builders — into the full
9
- * system prompt it sends to the generation LLM. core ships the pieces; the app
10
- * owns the assembly.
8
+ * @example
9
+ * const system = [getCommonInstructionsPartialPrompt(), getExpressionsPartialPrompt()].join("\n\n");
10
+ *
11
+ * @example
12
+ * getCommonInstructionsPartialPrompt({ note: "scopes.userCtx holds the signed-in user: name, plan; read-only." });
11
13
  */
12
- export function getCommonInstructionsPartialPrompt(_options = {}) {
13
- return INSTRUCTIONS;
14
+ export function getCommonInstructionsPartialPrompt({ maxListItems = 100, note, } = {}) {
15
+ return joinSections(INSTRUCTIONS.replaceAll("🔴MAX_LIST_ITEMS🔴", String(maxListItems)).trim(), noteSection(note));
14
16
  }
@@ -1,35 +1,27 @@
1
- import type { CombinedSpec, ComponentDefinition } from "../types.js";
1
+ import type { ComponentDefinition } from "../types.js";
2
+ import { type UrlPolicy } from "../url-policy.js";
3
+ /**
4
+ * Options for `getComponentsPartialPrompt`.
5
+ *
6
+ * @example
7
+ * const options: ComponentsPromptOptions = { definitions: defs, urlPolicy };
8
+ */
2
9
  export type ComponentsPromptOptions = {
3
- /** The component defs to advertise — the catalog's `allDefinitions` (plus any app-local defs). */
10
+ /** Your definitions: the same components the renderer has. One with `hidden: true` is left out. */
4
11
  definitions: ComponentDefinition[];
5
- /** Shared event schemas (each with a JSON Schema `$id`), rendered once under `# Common Events`. */
6
- commonEvents?: CombinedSpec[];
12
+ /** The renderer's `urlPolicy`, so the model writes URLs the renderer loads. Omitted: the renderer's defaults. */
13
+ urlPolicy?: UrlPolicy;
14
+ /** Your text, appended verbatim as this section's trailing `## Note`. */
15
+ note?: string;
7
16
  };
8
17
  /**
9
- * Render an array of component defs into the prompt's component section — a
10
- * `# Available Components` names list, an optional `# Common Events` block, then
11
- * `# Component Details`, one entry per visible def: its name + description, a
12
- * `Props:` list (each prop's type + description) and an `Event handlers:` list
13
- * (each handler's signature + description, a typed event's options described one
14
- * level deeper). Types come from `JSONSchemaToTs`; every description rides after
15
- * an em-dash, so nothing the author wrote on a prop, option, or handler is lost.
16
- *
17
- * `commonEvents` hoists handlers shared across many components (e.g. `onClick`)
18
- * out of every entry. Each passed schema must carry a JSON Schema `$id`; its
19
- * payload is rendered once as a named type under `# Common Events`, and a
20
- * component whose callback is one of them references it as `evt: <$id>` instead
21
- * of re-inlining the payload — killing the per-component duplication. A callback
22
- * is matched by its `$id`, so it stays library-agnostic (anything that emits
23
- * `$id` participates); non-common callbacks render inline as before.
24
- *
25
- * Host-only defs (`hidden: true`, e.g. RootFragment) are filtered out so the LLM
26
- * never sees host infrastructure in its component menu. Catalog-agnostic: the
27
- * caller passes whatever def set + common events it exposes.
18
+ * The component menu, from your definitions. A payload with an `id` prints once, under `## Common Events`; a URL
19
+ * prop adds `## URL Props`. Throws on a duplicate name, or on two different payloads with one `id`.
28
20
  *
29
- * Mirrors `getFunctionsPartialPrompt` — same `# Available X` / `# X Details`
30
- * two-section shape.
21
+ * @example
22
+ * getComponentsPartialPrompt({ definitions: defs });
31
23
  *
32
- * Throws on a duplicate component `name`, a common event whose schema has no
33
- * `$id`, or a duplicate common event `$id`.
24
+ * @example
25
+ * getComponentsPartialPrompt({ definitions: [...defs, BadgeDef], urlPolicy: { hosts: ["cdn.example.com"] } });
34
26
  */
35
- export declare function getComponentsPartialPrompt({ definitions: defs, commonEvents, }: ComponentsPromptOptions): string;
27
+ export declare function getComponentsPartialPrompt({ definitions: defs, urlPolicy, note }: ComponentsPromptOptions): string;