@uicast/core 0.0.1-beta.1 → 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +3 -6
- package/README.md +120 -1
- package/dist/build-elements-by-key.d.ts +13 -0
- package/dist/build-elements-by-key.js +39 -0
- package/dist/constants.d.ts +2 -0
- package/dist/constants.js +4 -0
- package/dist/create-component-definition.d.ts +26 -0
- package/dist/create-component-definition.js +74 -0
- package/dist/entry-error.d.ts +57 -0
- package/dist/entry-error.js +85 -0
- package/dist/entry-shape.d.ts +7 -0
- package/dist/entry-shape.js +88 -0
- package/dist/expr/evaluate.d.ts +5 -0
- package/dist/expr/evaluate.js +54 -0
- package/dist/expr/extract-deps.d.ts +4 -0
- package/dist/expr/extract-deps.js +30 -0
- package/dist/expr/plan-step-waves.d.ts +8 -0
- package/dist/expr/plan-step-waves.js +45 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +6 -0
- package/dist/internal.d.ts +12 -0
- package/dist/internal.js +13 -0
- package/dist/json-schema.d.ts +58 -0
- package/dist/json-schema.js +73 -0
- package/dist/prompt/format.d.ts +5 -0
- package/dist/prompt/format.js +22 -0
- package/dist/prompt/get-common-instructions-partial-prompt.d.ts +23 -0
- package/dist/prompt/get-common-instructions-partial-prompt.js +16 -0
- package/dist/prompt/get-components-partial-prompt.d.ts +27 -0
- package/dist/prompt/get-components-partial-prompt.js +133 -0
- package/dist/prompt/get-edit-request-prompt.d.ts +23 -0
- package/dist/prompt/get-edit-request-prompt.js +15 -0
- package/dist/prompt/get-error-recovery-prompt.d.ts +35 -0
- package/dist/prompt/get-error-recovery-prompt.js +26 -0
- package/dist/prompt/get-expressions-partial-prompt.d.ts +23 -0
- package/dist/prompt/get-expressions-partial-prompt.js +17 -0
- package/dist/prompt/get-functions-partial-prompt.d.ts +22 -0
- package/dist/prompt/get-functions-partial-prompt.js +58 -0
- package/dist/prompt/get-scope-partial-prompt.d.ts +24 -0
- package/dist/prompt/get-scope-partial-prompt.js +30 -0
- package/dist/prompt/index.d.ts +7 -0
- package/dist/prompt/index.js +7 -0
- package/dist/prompt/json-schema-to-ts.d.ts +6 -0
- package/dist/prompt/json-schema-to-ts.js +146 -0
- package/dist/prompt/md/EXPRESSIONS.json +1 -0
- package/dist/prompt/md/INSTRUCTIONS.json +1 -0
- package/dist/prompt/shared-types.d.ts +5 -0
- package/dist/prompt/shared-types.js +82 -0
- package/dist/scope/create-proxy-scope.d.ts +55 -0
- package/dist/scope/create-proxy-scope.js +295 -0
- package/dist/scope/parse-scope.d.ts +2 -0
- package/dist/scope/parse-scope.js +11 -0
- package/dist/scope/parse-set-address.d.ts +16 -0
- package/dist/scope/parse-set-address.js +52 -0
- package/dist/stream-json-lines.d.ts +14 -0
- package/dist/stream-json-lines.js +59 -0
- package/dist/types.d.ts +124 -0
- package/dist/types.js +19 -0
- package/dist/url-policy.d.ts +45 -0
- package/dist/url-policy.js +252 -0
- package/package.json +27 -18
- package/src/def/create-component-definition.ts +0 -21
- package/src/entry-error.ts +0 -104
- package/src/expr/analyze.ts +0 -190
- package/src/expr/ast-utils.ts +0 -28
- package/src/expr/evaluate.ts +0 -74
- package/src/expr/extract-deps.ts +0 -32
- package/src/expr/globals.ts +0 -118
- package/src/expr/safer-eval.ts +0 -233
- package/src/expr/validate.ts +0 -149
- package/src/index.ts +0 -28
- package/src/prompt/get-common-instructions-partial-prompt.ts +0 -20
- package/src/prompt/get-components-partial-prompt.ts +0 -153
- package/src/prompt/get-edit-request-prompt.ts +0 -37
- package/src/prompt/get-error-recovery-prompt.ts +0 -37
- package/src/prompt/get-expressions-partial-prompt.ts +0 -33
- package/src/prompt/get-functions-partial-prompt.ts +0 -62
- package/src/prompt/get-scope-partial-prompt.ts +0 -57
- package/src/prompt/index.ts +0 -32
- package/src/prompt/md/EXPRESSIONS.json +0 -1
- package/src/prompt/md/EXPRESSIONS.md +0 -29
- package/src/prompt/md/INSTRUCTIONS.json +0 -1
- package/src/prompt/md/INSTRUCTIONS.md +0 -172
- package/src/prompt-utils/json-schema-to-ts.ts +0 -225
- package/src/scope/create-proxy-scope.ts +0 -131
- package/src/scope/parse-scope.ts +0 -36
- package/src/stream/stream-json-lines.ts +0 -56
- package/src/types.ts +0 -124
- package/src/utils/utils.ts +0 -64
|
@@ -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
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
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
|
+
export { createProxyScope, type ReactiveProxy } from "./scope/create-proxy-scope.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
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { buildElementsByKey } from "./build-elements-by-key.js";
|
|
2
|
+
export { createComponentDefinition } from "./create-component-definition.js";
|
|
3
|
+
export { EntryError } from "./entry-error.js";
|
|
4
|
+
export { createProxyScope } from "./scope/create-proxy-scope.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";
|
package/dist/internal.js
ADDED
|
@@ -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");
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options for `getCommonInstructionsPartialPrompt`.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* const options: CommonInstructionsPromptOptions = { maxListItems: 50 };
|
|
6
|
+
*/
|
|
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;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { joinSections, noteSection } from "./format.js";
|
|
2
|
+
// Generated from `md/INSTRUCTIONS.md`: run `npm run md-to-json` after editing it.
|
|
3
|
+
import INSTRUCTIONS from "./md/INSTRUCTIONS.json" with { type: "json" };
|
|
4
|
+
/**
|
|
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.
|
|
7
|
+
*
|
|
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." });
|
|
13
|
+
*/
|
|
14
|
+
export function getCommonInstructionsPartialPrompt({ maxListItems = 100, note, } = {}) {
|
|
15
|
+
return joinSections(INSTRUCTIONS.replaceAll("🔴MAX_LIST_ITEMS🔴", String(maxListItems)).trim(), noteSection(note));
|
|
16
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
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
|
+
*/
|
|
9
|
+
export type ComponentsPromptOptions = {
|
|
10
|
+
/** Your definitions: the same components the renderer has. One with `hidden: true` is left out. */
|
|
11
|
+
definitions: ComponentDefinition[];
|
|
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;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
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`.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* getComponentsPartialPrompt({ definitions: defs });
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* getComponentsPartialPrompt({ definitions: [...defs, BadgeDef], urlPolicy: { hosts: ["cdn.example.com"] } });
|
|
26
|
+
*/
|
|
27
|
+
export declare function getComponentsPartialPrompt({ definitions: defs, urlPolicy, note }: ComponentsPromptOptions): string;
|
|
@@ -0,0 +1,133 @@
|
|
|
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]) => {
|
|
10
|
+
const optional = required.includes(name) ? "" : "?";
|
|
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)}`;
|
|
14
|
+
});
|
|
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
|
+
};
|
|
72
|
+
/**
|
|
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`.
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* getComponentsPartialPrompt({ definitions: defs });
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* getComponentsPartialPrompt({ definitions: [...defs, BadgeDef], urlPolicy: { hosts: ["cdn.example.com"] } });
|
|
81
|
+
*/
|
|
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);
|
|
88
|
+
}
|
|
89
|
+
const visible = defs.filter((def) => !def.hidden);
|
|
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}`];
|
|
122
|
+
});
|
|
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));
|
|
133
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options for `getEditRequestPrompt`.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* const options: EditRequestPromptOptions = { request: "Add a total row.", missingKeys: ["totals"] };
|
|
6
|
+
*/
|
|
7
|
+
export type EditRequestPromptOptions = {
|
|
8
|
+
/** The change the user asked for, in their words. */
|
|
9
|
+
request: string;
|
|
10
|
+
/** Keys named in `children` but never emitted, as after a cut-off response. The model is told to emit them. */
|
|
11
|
+
missingKeys?: string[];
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
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.
|
|
16
|
+
*
|
|
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
|
+
*/
|
|
23
|
+
export declare function getEditRequestPrompt({ request, missingKeys }: EditRequestPromptOptions): string;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { joinSections } from "./format.js";
|
|
2
|
+
/**
|
|
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.
|
|
5
|
+
*
|
|
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
|
+
*/
|
|
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\`.`);
|
|
15
|
+
}
|
|
@@ -0,0 +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
|
+
*/
|
|
8
|
+
export type RenderFailure = {
|
|
9
|
+
/** The failed element's key. */
|
|
10
|
+
key: string;
|
|
11
|
+
/** What went wrong, e.g. the error's `message`. */
|
|
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;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Options for `getErrorRecoveryPrompt`.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* const options: ErrorRecoveryPromptOptions = { failures: [{ key: "chart", message: error.message }] };
|
|
21
|
+
*/
|
|
22
|
+
export type ErrorRecoveryPromptOptions = {
|
|
23
|
+
/** One per failed element. */
|
|
24
|
+
failures: RenderFailure[];
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
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.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* getErrorRecoveryPrompt({
|
|
32
|
+
* failures: [{ key: "chart", message: "chartData.map is not a function", reason: "expression-runtime" }],
|
|
33
|
+
* });
|
|
34
|
+
*/
|
|
35
|
+
export declare function getErrorRecoveryPrompt({ failures }: ErrorRecoveryPromptOptions): string;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { FAULT_BY_REASON, REASON_DESCRIPTIONS } from "../entry-error.js";
|
|
2
|
+
/**
|
|
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.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* getErrorRecoveryPrompt({
|
|
8
|
+
* failures: [{ key: "chart", message: "chartData.map is not a function", reason: "expression-runtime" }],
|
|
9
|
+
* });
|
|
10
|
+
*/
|
|
11
|
+
export function getErrorRecoveryPrompt({ failures }) {
|
|
12
|
+
const list = failures
|
|
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
|
+
})
|
|
20
|
+
.join("\n");
|
|
21
|
+
return [
|
|
22
|
+
"Rendered UI has errors:",
|
|
23
|
+
list,
|
|
24
|
+
"Re-emit each failed entry fixed, same `key`. Fix cause: when it comes from another entry (like `seed` elsewhere), fix that entry too.",
|
|
25
|
+
].join("\n\n");
|
|
26
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options for `getExpressionsPartialPrompt`.
|
|
3
|
+
*
|
|
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();
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* getExpressionsPartialPrompt({ maxLength: 500 }); // with new Evaluator({ maxSourceLength: 500 })
|
|
22
|
+
*/
|
|
23
|
+
export declare function getExpressionsPartialPrompt({ maxLength, note, }?: ExpressionsPromptOptions): string;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ALLOWED_GLOBALS, DEFAULT_MAX_SOURCE_LENGTH } from "@uicast/expr/internal";
|
|
2
|
+
import { joinSections, noteSection } from "./format.js";
|
|
3
|
+
import EXPRESSIONS from "./md/EXPRESSIONS.json" with { type: "json" };
|
|
4
|
+
/**
|
|
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.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* getExpressionsPartialPrompt();
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* getExpressionsPartialPrompt({ maxLength: 500 }); // with new Evaluator({ maxSourceLength: 500 })
|
|
13
|
+
*/
|
|
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));
|
|
17
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
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
|
+
*/
|
|
8
|
+
export type FunctionsPromptOptions = {
|
|
9
|
+
/** Your host functions: the array the evaluator binds. An empty one adds nothing. */
|
|
10
|
+
functions: StandardToolV0[];
|
|
11
|
+
/** Your text, appended verbatim as this section's trailing `## Note`. */
|
|
12
|
+
note?: string;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
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`.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* getFunctionsPartialPrompt({ functions: tools }); // the same array as new Evaluator({ functions: tools })
|
|
21
|
+
*/
|
|
22
|
+
export declare function getFunctionsPartialPrompt({ functions, note }: FunctionsPromptOptions): string;
|