@vertesia/studio-utils 1.5.0-dev.20260910.052912Z → 1.5.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 (40) hide show
  1. package/lib/conditions/index.d.ts +1 -0
  2. package/lib/conditions/index.d.ts.map +1 -1
  3. package/lib/conditions/index.js +1 -0
  4. package/lib/conditions/index.js.map +1 -1
  5. package/lib/conditions/principal-context.d.ts +55 -0
  6. package/lib/conditions/principal-context.d.ts.map +1 -0
  7. package/lib/conditions/principal-context.js +81 -0
  8. package/lib/conditions/principal-context.js.map +1 -0
  9. package/lib/index.d.ts +2 -1
  10. package/lib/index.d.ts.map +1 -1
  11. package/lib/index.js +4 -1
  12. package/lib/index.js.map +1 -1
  13. package/lib/prompts/extract-vars.d.ts +44 -12
  14. package/lib/prompts/extract-vars.d.ts.map +1 -1
  15. package/lib/prompts/extract-vars.js +165 -75
  16. package/lib/prompts/extract-vars.js.map +1 -1
  17. package/lib/prompts/render.d.ts +7 -9
  18. package/lib/prompts/render.d.ts.map +1 -1
  19. package/lib/prompts/render.js +12 -15
  20. package/lib/prompts/render.js.map +1 -1
  21. package/lib/prompts/validate.d.ts +8 -2
  22. package/lib/prompts/validate.d.ts.map +1 -1
  23. package/lib/prompts/validate.js +87 -50
  24. package/lib/prompts/validate.js.map +1 -1
  25. package/lib/roles/system.d.ts.map +1 -1
  26. package/lib/roles/system.js +15 -0
  27. package/lib/roles/system.js.map +1 -1
  28. package/lib/vertesia-studio-utils.js +2 -1
  29. package/lib/vertesia-studio-utils.js.map +1 -1
  30. package/package.json +6 -5
  31. package/src/conditions/index.ts +6 -0
  32. package/src/conditions/principal-context.ts +122 -0
  33. package/src/index.ts +20 -1
  34. package/src/prompts/extract-vars.ts +212 -68
  35. package/src/prompts/render.test.ts +13 -2
  36. package/src/prompts/render.ts +24 -15
  37. package/src/prompts/validate.test.ts +115 -0
  38. package/src/prompts/validate.ts +113 -56
  39. package/src/roles/index.test.ts +9 -9
  40. package/src/roles/system.ts +16 -0
@@ -1,2 +1,3 @@
1
1
  export { matchConditions, matchLike, resolveConditions, resolvePath } from './match.js';
2
+ export { buildPrincipalContext, type MergedUserFields, type MergeSource, mergeUserFields, } from './principal-context.js';
2
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/conditions/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/conditions/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACxF,OAAO,EACH,qBAAqB,EACrB,KAAK,gBAAgB,EACrB,KAAK,WAAW,EAChB,eAAe,GAClB,MAAM,wBAAwB,CAAC"}
@@ -1,2 +1,3 @@
1
1
  export { matchConditions, matchLike, resolveConditions, resolvePath } from './match.js';
2
+ export { buildPrincipalContext, mergeUserFields, } from './principal-context.js';
2
3
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/conditions/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/conditions/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACxF,OAAO,EACH,qBAAqB,EAGrB,eAAe,GAClB,MAAM,wBAAwB,CAAC"}
@@ -0,0 +1,55 @@
1
+ import type { AbacPrincipalContext, PrincipalKind } from '@vertesia/common';
2
+ /**
3
+ * The shared principal-context builder, consumed by both the token server (JWT `$principal.*`
4
+ * resolution at mint time) and studio-server (the `/iam/users/identity` endpoint). It is the single
5
+ * home for logic that used to be duplicated in `apps/token-server` and `apps/studio-server` and kept
6
+ * in sync by hand; the produced {@link AbacPrincipalContext} is exactly the shape ABAC rules resolve
7
+ * against.
8
+ *
9
+ * Browser-safe (pure, no Node built-ins): the UI condition editor lives downstream of the same
10
+ * `@vertesia/common` shape, and this package must stay loadable in the browser bundle.
11
+ */
12
+ /**
13
+ * A user, group, or api-key record as it comes out of Mongo, where every optional field can be null
14
+ * as well as absent — which the inferred persistence types say out loud. The merge treats null and
15
+ * absent identically, so nothing about the resulting context changes; what changes is that the
16
+ * nullability is handled rather than asserted away at the call site.
17
+ */
18
+ export interface MergeSource {
19
+ properties?: Record<string, unknown> | null;
20
+ clearance?: number | null;
21
+ compartments?: string[] | null;
22
+ tags?: string[] | null;
23
+ }
24
+ export interface MergedUserFields {
25
+ properties?: Record<string, unknown>;
26
+ clearance?: number;
27
+ compartments?: string[];
28
+ tags?: string[];
29
+ }
30
+ /**
31
+ * Merge properties, clearance, compartments, and tags from groups + the principal.
32
+ *
33
+ * - properties: array values are deduplicated union, scalars use last-write-wins (principal wins)
34
+ * - clearance: max across all defined values (most permissive wins)
35
+ * - compartments / tags: deduplicated union of all arrays
36
+ *
37
+ * Used for the user token path (user + covering groups). The api-key path has no groups to merge and
38
+ * passes the key document straight to {@link buildPrincipalContext} as `fields`.
39
+ */
40
+ export declare function mergeUserFields(groups: MergeSource[], user: MergeSource): MergedUserFields;
41
+ /**
42
+ * Build the {@link AbacPrincipalContext} consumed by ResourceSet/PrincipalSet condition evaluation
43
+ * and mirrored into the JWT for runtime `$principal.*` resolution.
44
+ *
45
+ * - user: pass `mergeUserFields(groups, user)` as fields, the user's email, and the user id
46
+ * - apikey: pass the api key document itself (it carries optional properties/clearance/compartments)
47
+ * and the api key id
48
+ *
49
+ * Defaults are applied so condition operators have well-defined behaviour when the principal has no
50
+ * merged value: clearance → 0, compartments → [], tags → [], properties → {}, id → '', email → ''.
51
+ * `email` is published optional on the wire but defaulted to '' here so `$principal.email` resolves
52
+ * to a string rather than undefined during rule evaluation.
53
+ */
54
+ export declare function buildPrincipalContext(kind: PrincipalKind, fields: MergeSource, email?: string | null, id?: string): AbacPrincipalContext;
55
+ //# sourceMappingURL=principal-context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"principal-context.d.ts","sourceRoot":"","sources":["../../src/conditions/principal-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAE5E;;;;;;;;;GASG;AAEH;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC5C,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAC/B,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;CAC1B;AAED,MAAM,WAAW,gBAAgB;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,WAAW,EAAE,EAAE,IAAI,EAAE,WAAW,GAAG,gBAAgB,CAgD1F;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CACjC,IAAI,EAAE,aAAa,EACnB,MAAM,EAAE,WAAW,EACnB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,EACrB,EAAE,CAAC,EAAE,MAAM,GACZ,oBAAoB,CAUtB"}
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Merge properties, clearance, compartments, and tags from groups + the principal.
3
+ *
4
+ * - properties: array values are deduplicated union, scalars use last-write-wins (principal wins)
5
+ * - clearance: max across all defined values (most permissive wins)
6
+ * - compartments / tags: deduplicated union of all arrays
7
+ *
8
+ * Used for the user token path (user + covering groups). The api-key path has no groups to merge and
9
+ * passes the key document straight to {@link buildPrincipalContext} as `fields`.
10
+ */
11
+ export function mergeUserFields(groups, user) {
12
+ const mergedProps = {};
13
+ let maxClearance;
14
+ const allCompartments = new Set();
15
+ const allTags = new Set();
16
+ for (const source of [...groups, user]) {
17
+ if (source.properties) {
18
+ for (const [key, value] of Object.entries(source.properties)) {
19
+ if (Array.isArray(value)) {
20
+ const existing = mergedProps[key];
21
+ if (Array.isArray(existing)) {
22
+ const set = new Set([...existing, ...value]);
23
+ mergedProps[key] = Array.from(set);
24
+ }
25
+ else {
26
+ mergedProps[key] = [...value];
27
+ }
28
+ }
29
+ else {
30
+ mergedProps[key] = value;
31
+ }
32
+ }
33
+ }
34
+ // `!= null` rather than `!== undefined`: a stored null means "no clearance recorded", the
35
+ // same as an absent field, and must not reach Math.max where it would read as 0.
36
+ if (source.clearance != null) {
37
+ maxClearance = maxClearance !== undefined ? Math.max(maxClearance, source.clearance) : source.clearance;
38
+ }
39
+ if (source.compartments) {
40
+ for (const c of source.compartments) {
41
+ allCompartments.add(c);
42
+ }
43
+ }
44
+ if (source.tags) {
45
+ for (const t of source.tags) {
46
+ allTags.add(t);
47
+ }
48
+ }
49
+ }
50
+ return {
51
+ properties: Object.keys(mergedProps).length > 0 ? mergedProps : undefined,
52
+ clearance: maxClearance,
53
+ compartments: allCompartments.size > 0 ? Array.from(allCompartments) : undefined,
54
+ tags: allTags.size > 0 ? Array.from(allTags) : undefined,
55
+ };
56
+ }
57
+ /**
58
+ * Build the {@link AbacPrincipalContext} consumed by ResourceSet/PrincipalSet condition evaluation
59
+ * and mirrored into the JWT for runtime `$principal.*` resolution.
60
+ *
61
+ * - user: pass `mergeUserFields(groups, user)` as fields, the user's email, and the user id
62
+ * - apikey: pass the api key document itself (it carries optional properties/clearance/compartments)
63
+ * and the api key id
64
+ *
65
+ * Defaults are applied so condition operators have well-defined behaviour when the principal has no
66
+ * merged value: clearance → 0, compartments → [], tags → [], properties → {}, id → '', email → ''.
67
+ * `email` is published optional on the wire but defaulted to '' here so `$principal.email` resolves
68
+ * to a string rather than undefined during rule evaluation.
69
+ */
70
+ export function buildPrincipalContext(kind, fields, email, id) {
71
+ return {
72
+ kind,
73
+ id: id ?? '',
74
+ clearance: fields.clearance ?? 0,
75
+ compartments: fields.compartments ?? [],
76
+ email: email ?? '',
77
+ tags: fields.tags ?? [],
78
+ properties: fields.properties ?? {},
79
+ };
80
+ }
81
+ //# sourceMappingURL=principal-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"principal-context.js","sourceRoot":"","sources":["../../src/conditions/principal-context.ts"],"names":[],"mappings":"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"}
package/lib/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
+ export { describeTemplateSystemVariables, HANDLEBARS_HELPER_NAMES, isTemplateSystemVariable, TEMPLATE_SYSTEM_VARIABLE_NAMES, TEMPLATE_SYSTEM_VARIABLES, type TemplateSystemContext, type TemplateSystemVariable, type TemplateSystemVariableName, withTemplateSystemVariables, } from '@vertesia/jst';
1
2
  export { substituteEndpoints } from './apps/index.js';
2
- export { extractHandlebarsVariables } from './prompts/extract-vars.js';
3
+ export { analyzeHandlebarsTemplate, extractHandlebarsVariables, type HandlebarsHelperMisuse, type HandlebarsTemplateAnalysis, type HandlebarsVariableReference, } from './prompts/extract-vars.js';
3
4
  export { generateMockData } from './prompts/mock-data.js';
4
5
  export { executeHandlebars, executeJST, renderPrompt, renderSegments, renderSegmentsOrErrors, renderTemplate, type SegmentPreview, } from './prompts/render.js';
5
6
  export { type PromptValidationInput, type PromptValidationIssue, type PromptValidationIssueSeverity, type PromptValidationIssueType, type PromptValidationResult, validatePrompt, } from './prompts/validate.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAAE,0BAA0B,EAAE,MAAM,2BAA2B,CAAC;AACvE,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EACH,iBAAiB,EACjB,UAAU,EACV,YAAY,EACZ,cAAc,EACd,sBAAsB,EACtB,cAAc,EACd,KAAK,cAAc,GACtB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACH,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,EAC1B,KAAK,6BAA6B,EAClC,KAAK,yBAAyB,EAC9B,KAAK,sBAAsB,EAC3B,cAAc,GACjB,MAAM,uBAAuB,CAAC;AAC/B,cAAc,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EACH,+BAA+B,EAC/B,uBAAuB,EACvB,wBAAwB,EACxB,8BAA8B,EAC9B,yBAAyB,EACzB,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,EAC3B,KAAK,0BAA0B,EAC/B,2BAA2B,GAC9B,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EACH,yBAAyB,EACzB,0BAA0B,EAC1B,KAAK,sBAAsB,EAC3B,KAAK,0BAA0B,EAC/B,KAAK,2BAA2B,GACnC,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EACH,iBAAiB,EACjB,UAAU,EACV,YAAY,EACZ,cAAc,EACd,sBAAsB,EACtB,cAAc,EACd,KAAK,cAAc,GACtB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACH,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,EAC1B,KAAK,6BAA6B,EAClC,KAAK,yBAAyB,EAC9B,KAAK,sBAAsB,EAC3B,cAAc,GACjB,MAAM,uBAAuB,CAAC;AAC/B,cAAc,kBAAkB,CAAC"}
package/lib/index.js CHANGED
@@ -1,5 +1,8 @@
1
+ // The template runtime's registry, re-exported so consumers validating or documenting prompts
2
+ // read the same names the renderer uses.
3
+ export { describeTemplateSystemVariables, HANDLEBARS_HELPER_NAMES, isTemplateSystemVariable, TEMPLATE_SYSTEM_VARIABLE_NAMES, TEMPLATE_SYSTEM_VARIABLES, withTemplateSystemVariables, } from '@vertesia/jst';
1
4
  export { substituteEndpoints } from './apps/index.js';
2
- export { extractHandlebarsVariables } from './prompts/extract-vars.js';
5
+ export { analyzeHandlebarsTemplate, extractHandlebarsVariables, } from './prompts/extract-vars.js';
3
6
  export { generateMockData } from './prompts/mock-data.js';
4
7
  export { executeHandlebars, executeJST, renderPrompt, renderSegments, renderSegmentsOrErrors, renderTemplate, } from './prompts/render.js';
5
8
  export { validatePrompt, } from './prompts/validate.js';
package/lib/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAAE,0BAA0B,EAAE,MAAM,2BAA2B,CAAC;AACvE,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EACH,iBAAiB,EACjB,UAAU,EACV,YAAY,EACZ,cAAc,EACd,sBAAsB,EACtB,cAAc,GAEjB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAMH,cAAc,GACjB,MAAM,uBAAuB,CAAC;AAC/B,cAAc,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,8FAA8F;AAC9F,yCAAyC;AACzC,OAAO,EACH,+BAA+B,EAC/B,uBAAuB,EACvB,wBAAwB,EACxB,8BAA8B,EAC9B,yBAAyB,EAIzB,2BAA2B,GAC9B,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EACH,yBAAyB,EACzB,0BAA0B,GAI7B,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EACH,iBAAiB,EACjB,UAAU,EACV,YAAY,EACZ,cAAc,EACd,sBAAsB,EACtB,cAAc,GAEjB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAMH,cAAc,GACjB,MAAM,uBAAuB,CAAC;AAC/B,cAAc,kBAAkB,CAAC"}
@@ -1,19 +1,51 @@
1
+ /** A read of a root-level template variable. */
2
+ export interface HandlebarsVariableReference {
3
+ /** Root identifier: `customer` for `{{customer.name}}`, `{{../customer}}` or `{{@root.customer}}`. */
4
+ name: string;
5
+ /** The path as written, e.g. `customer.name`. */
6
+ expression: string;
7
+ /** True when the path reads a property of the variable (`customer.name`). */
8
+ hasPath: boolean;
9
+ /** Source of the tag that contains the read, e.g. `{{#if customer}}`. */
10
+ tag: string;
11
+ }
12
+ /** A helper name used where Handlebars will not call it. */
13
+ export interface HandlebarsHelperMisuse {
14
+ helper: string;
15
+ /**
16
+ * - `used_as_value`: passed as an argument (`{{#if stringify}}`), where Handlebars reads the data.
17
+ * - `missing_arguments`: called bare (`{{stringify}}`) with none of the arguments it needs.
18
+ */
19
+ problem: 'used_as_value' | 'missing_arguments';
20
+ tag: string;
21
+ }
22
+ export interface HandlebarsTemplateAnalysis {
23
+ references: HandlebarsVariableReference[];
24
+ helperMisuses: HandlebarsHelperMisuse[];
25
+ }
1
26
  /**
2
- * Extract the set of root-level variable names referenced by a Handlebars template.
27
+ * Analyze the variables and helpers a Handlebars template uses, resolving names the way the
28
+ * prompt renderer does (helpers and system variables come from `@vertesia/jst`).
3
29
  *
4
- * - `{{foo}}` → `foo`
5
- * - `{{obj.bar.baz}}` → `obj` (only the root identifier; nested access doesn't add `bar` or `baz`)
6
- * - `{{#if cond}}{{name}}{{/if}}` → `cond`, `name`
7
- * - `{{#each items as |item|}}{{item.name}}{{/each}}` → `items` (NOT `item` — bound by `as |item|`)
8
- * - `{{lookup obj key}}` → `obj`, `key` (helper name skipped)
9
- * - `{{@index}}`, `{{this}}` → ignored (built-in data vars / self)
30
+ * Scoping rules:
31
+ * - Inside `{{#each}}` / `{{#with}}` (and `{{#section}}` over data), bare names read the current item,
32
+ * whose shape the input schema does not describe — they are not reported. `../x` and `@root.x`
33
+ * read the enclosing or root context and are reported.
34
+ * - `as |x|` block params are local bindings, not variables.
35
+ * - `{{@index}}` and other `@`-data (except `@root.x`) are runtime values, not variables.
10
36
  *
11
- * Helper names in mustaches and block heads (`{{customHelper x}}`, `{{#if x}}`) are NOT added to
12
- * the set — only the arguments and inner template references are. This matches the typical use
13
- * case for prompt validation: detect which schema properties the template actually reads.
37
+ * Returns null when the template does not parse; the render check reports the syntax error.
38
+ */
39
+ export declare function analyzeHandlebarsTemplate(template: string): HandlebarsTemplateAnalysis | null;
40
+ /**
41
+ * Extract the set of root-level input variable names a Handlebars template reads.
42
+ *
43
+ * - `{{foo}}` → `foo`; `{{obj.bar.baz}}` → `obj`
44
+ * - `{{#each items as |item|}}{{item.name}}{{/each}}` → `items` (`item` is a block param)
45
+ * - `{{lookup obj key}}` → `obj`, `key` (helper name skipped)
46
+ * - `{{@index}}`, `{{this}}`, and bare names inside `#each`/`#with` → ignored
14
47
  *
15
- * Returns an empty set on parse failure — callers should also run a syntactic render check
16
- * (e.g. `executeHandlebars`) to surface parse errors separately.
48
+ * Returns an empty set on parse failure.
17
49
  */
18
50
  export declare function extractHandlebarsVariables(template: string): Set<string>;
19
51
  //# sourceMappingURL=extract-vars.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"extract-vars.d.ts","sourceRoot":"","sources":["../../src/prompts/extract-vars.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CA0FxE"}
1
+ {"version":3,"file":"extract-vars.d.ts","sourceRoot":"","sources":["../../src/prompts/extract-vars.ts"],"names":[],"mappings":"AAGA,gDAAgD;AAChD,MAAM,WAAW,2BAA2B;IACxC,sGAAsG;IACtG,IAAI,EAAE,MAAM,CAAC;IACb,iDAAiD;IACjD,UAAU,EAAE,MAAM,CAAC;IACnB,6EAA6E;IAC7E,OAAO,EAAE,OAAO,CAAC;IACjB,yEAAyE;IACzE,GAAG,EAAE,MAAM,CAAC;CACf;AAED,4DAA4D;AAC5D,MAAM,WAAW,sBAAsB;IACnC,MAAM,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,OAAO,EAAE,eAAe,GAAG,mBAAmB,CAAC;IAC/C,GAAG,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,0BAA0B;IACvC,UAAU,EAAE,2BAA2B,EAAE,CAAC;IAC1C,aAAa,EAAE,sBAAsB,EAAE,CAAC;CAC3C;AAyCD;;;;;;;;;;;;GAYG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,GAAG,0BAA0B,GAAG,IAAI,CA2J7F;AAED;;;;;;;;;GASG;AACH,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAGxE"}
@@ -1,111 +1,201 @@
1
+ import { HANDLEBARS_CONTEXT_HELPERS, isHandlebarsHelper, isTemplateSystemVariable } from '@vertesia/jst';
1
2
  import Handlebars from 'handlebars';
3
+ function isPath(node) {
4
+ return !!node && typeof node === 'object' && node.type === 'PathExpression';
5
+ }
6
+ /** `this.x`, `./x` and `this` read the context explicitly — Handlebars never treats them as helpers. */
7
+ function isScoped(path) {
8
+ return /^\.|this\b/.test(path.original);
9
+ }
10
+ /** A single bare identifier — the only form Handlebars may resolve to a helper. */
11
+ function isSimpleId(path) {
12
+ return path.parts.length === 1 && !isScoped(path) && !path.data && path.depth === 0;
13
+ }
14
+ function lineOffsets(template) {
15
+ const offsets = [0];
16
+ for (let i = 0; i < template.length; i++) {
17
+ if (template[i] === '\n')
18
+ offsets.push(i + 1);
19
+ }
20
+ return offsets;
21
+ }
2
22
  /**
3
- * Extract the set of root-level variable names referenced by a Handlebars template.
23
+ * Analyze the variables and helpers a Handlebars template uses, resolving names the way the
24
+ * prompt renderer does (helpers and system variables come from `@vertesia/jst`).
4
25
  *
5
- * - `{{foo}}` → `foo`
6
- * - `{{obj.bar.baz}}` → `obj` (only the root identifier; nested access doesn't add `bar` or `baz`)
7
- * - `{{#if cond}}{{name}}{{/if}}` → `cond`, `name`
8
- * - `{{#each items as |item|}}{{item.name}}{{/each}}` → `items` (NOT `item` — bound by `as |item|`)
9
- * - `{{lookup obj key}}` → `obj`, `key` (helper name skipped)
10
- * - `{{@index}}`, `{{this}}` → ignored (built-in data vars / self)
26
+ * Scoping rules:
27
+ * - Inside `{{#each}}` / `{{#with}}` (and `{{#section}}` over data), bare names read the current item,
28
+ * whose shape the input schema does not describe — they are not reported. `../x` and `@root.x`
29
+ * read the enclosing or root context and are reported.
30
+ * - `as |x|` block params are local bindings, not variables.
31
+ * - `{{@index}}` and other `@`-data (except `@root.x`) are runtime values, not variables.
11
32
  *
12
- * Helper names in mustaches and block heads (`{{customHelper x}}`, `{{#if x}}`) are NOT added to
13
- * the set — only the arguments and inner template references are. This matches the typical use
14
- * case for prompt validation: detect which schema properties the template actually reads.
15
- *
16
- * Returns an empty set on parse failure — callers should also run a syntactic render check
17
- * (e.g. `executeHandlebars`) to surface parse errors separately.
33
+ * Returns null when the template does not parse; the render check reports the syntax error.
18
34
  */
19
- export function extractHandlebarsVariables(template) {
35
+ export function analyzeHandlebarsTemplate(template) {
20
36
  let ast;
21
37
  try {
22
38
  ast = Handlebars.parse(template);
23
39
  }
24
40
  catch {
25
- return new Set();
41
+ return null;
26
42
  }
27
- const variables = new Set();
28
- const localScopes = [];
29
- const isLocal = (name) => localScopes.some((scope) => scope.has(name));
43
+ const offsets = lineOffsets(template);
44
+ const tagAt = (loc) => {
45
+ if (!loc)
46
+ return '';
47
+ const start = (offsets[loc.start.line - 1] ?? 0) + loc.start.column;
48
+ const end = template.indexOf('}}', start);
49
+ if (end < 0)
50
+ return template.slice(start);
51
+ return template.slice(start, template[end + 2] === '}' ? end + 3 : end + 2);
52
+ };
53
+ const references = [];
54
+ const helperMisuses = [];
55
+ const contexts = ['root'];
56
+ const blockParams = [];
57
+ let tag = '';
58
+ const isBlockParam = (name) => blockParams.some((scope) => scope.has(name));
59
+ /** Record a data read of `path`, if it reads the root input. */
60
+ const readData = (path) => {
61
+ if (path.data) {
62
+ // `@root.x` reads the input root; `@index`, `@key`, ... are runtime values.
63
+ if (path.parts[0] === 'root' && path.parts.length > 1) {
64
+ references.push({
65
+ name: path.parts[1],
66
+ expression: path.original,
67
+ hasPath: path.parts.length > 2,
68
+ tag,
69
+ });
70
+ }
71
+ return;
72
+ }
73
+ if (path.parts.length === 0)
74
+ return; // `this` / `.`
75
+ const root = path.parts[0];
76
+ if (path.depth === 0 && !isScoped(path) && isBlockParam(root))
77
+ return;
78
+ // Handlebars stops at the root context when `../` goes past it.
79
+ const context = contexts[Math.max(0, contexts.length - 1 - path.depth)];
80
+ if (context !== 'root')
81
+ return;
82
+ references.push({ name: root, expression: path.original, hasPath: path.parts.length > 1, tag });
83
+ };
84
+ /** A path in argument position (params, hash values): Handlebars reads data, never calls a helper. */
85
+ const visitArgument = (node) => {
86
+ if (isPath(node)) {
87
+ const name = node.parts[0];
88
+ // System variables are injected as data too, so they resolve as arguments.
89
+ if (isSimpleId(node) &&
90
+ !isBlockParam(name) &&
91
+ isHandlebarsHelper(name) &&
92
+ !isTemplateSystemVariable(name)) {
93
+ helperMisuses.push({ helper: name, problem: 'used_as_value', tag });
94
+ return;
95
+ }
96
+ readData(node);
97
+ return;
98
+ }
99
+ visit(node);
100
+ };
101
+ const visitCall = (n) => {
102
+ const params = n.params ?? [];
103
+ for (const p of params)
104
+ visitArgument(p);
105
+ const pairs = n.hash?.pairs ?? [];
106
+ for (const pair of pairs)
107
+ visitArgument(pair.value);
108
+ };
109
+ const hasArguments = (n) => (n.params?.length ?? 0) > 0 ||
110
+ (n.hash?.pairs?.length ?? 0) > 0;
30
111
  const visit = (node) => {
31
112
  if (!node || typeof node !== 'object')
32
113
  return;
33
114
  const n = node;
34
115
  switch (n.type) {
35
116
  case 'Program': {
36
- const body = n.body;
37
- if (body)
38
- for (const stmt of body)
39
- visit(stmt);
117
+ for (const stmt of n.body ?? [])
118
+ visit(stmt);
119
+ break;
120
+ }
121
+ case 'MustacheStatement': {
122
+ tag = tagAt(n.loc);
123
+ const path = n.path;
124
+ if (hasArguments(n)) {
125
+ // `{{helper arg}}`: the head is a helper name (a missing one fails the render check).
126
+ visitCall(n);
127
+ }
128
+ else if (isPath(path)) {
129
+ // A bare `{{name}}` calls a helper of that name when there is one, else reads data.
130
+ // System variables are helpers returning their injected value, meant to be used bare.
131
+ const name = path.parts[0];
132
+ if (isSimpleId(path) && !isBlockParam(name) && isHandlebarsHelper(name)) {
133
+ if (!isTemplateSystemVariable(name)) {
134
+ helperMisuses.push({ helper: name, problem: 'missing_arguments', tag });
135
+ }
136
+ }
137
+ else {
138
+ readData(path);
139
+ }
140
+ }
141
+ else {
142
+ visit(path);
143
+ }
40
144
  break;
41
145
  }
42
146
  case 'BlockStatement': {
43
- // n.path is the helper name (`#if`, `#each`, `#customBlock`) — never a variable.
44
- // Visit params + hash to capture variables passed to the block helper.
45
- const params = n.params;
46
- if (params)
47
- for (const p of params)
48
- visit(p);
49
- visit(n.hash);
50
- // Track `as |x y|` block params — these are local bindings, not variables.
147
+ tag = tagAt(n.loc);
148
+ const path = n.path;
149
+ const helper = isPath(path) && isSimpleId(path) ? path.parts[0] : undefined;
150
+ let itemContext;
151
+ if (hasArguments(n) || (helper && isHandlebarsHelper(helper))) {
152
+ visitCall(n);
153
+ itemContext = !!helper && HANDLEBARS_CONTEXT_HELPERS.includes(helper);
154
+ }
155
+ else {
156
+ // `{{#section}}…{{/section}}` over data: iterates or enters `section`.
157
+ if (isPath(path))
158
+ readData(path);
159
+ itemContext = true;
160
+ }
51
161
  const program = n.program;
52
162
  const inverse = n.inverse;
53
- const scope = new Set([...(program?.blockParams ?? []), ...(inverse?.blockParams ?? [])]);
54
- localScopes.push(scope);
163
+ blockParams.push(new Set(program?.blockParams ?? []));
164
+ contexts.push(itemContext ? 'item' : contexts[contexts.length - 1]);
55
165
  visit(n.program);
166
+ contexts.pop();
167
+ blockParams.pop();
168
+ // `{{else}}` renders with the enclosing context.
169
+ blockParams.push(new Set(inverse?.blockParams ?? []));
56
170
  visit(n.inverse);
57
- localScopes.pop();
58
- break;
59
- }
60
- case 'MustacheStatement': {
61
- // Bare mustache (no params, no hash) → it's a variable reference.
62
- // With params/hash, it's a helper call — skip the path (helper name), visit args.
63
- const params = n.params;
64
- const hash = n.hash;
65
- const isHelperCall = (params?.length ?? 0) > 0 || (hash?.pairs?.length ?? 0) > 0;
66
- if (!isHelperCall) {
67
- visit(n.path);
68
- }
69
- if (params)
70
- for (const p of params)
71
- visit(p);
72
- visit(hash);
171
+ blockParams.pop();
73
172
  break;
74
173
  }
75
174
  case 'SubExpression': {
76
- // (helper arg1 arg2) — helper name in path is skipped, args carry variables.
77
- const params = n.params;
78
- if (params)
79
- for (const p of params)
80
- visit(p);
81
- visit(n.hash);
175
+ // `(helper arg)`: the head is a call, like a bare `{{helper}}`.
176
+ visitCall(n);
82
177
  break;
83
178
  }
84
- case 'PathExpression': {
85
- if (n.data)
86
- break; // `@-data` references (@index, @key, etc.)
87
- const parts = n.parts;
88
- if (!parts || parts.length === 0)
89
- break; // `this` / `.`
90
- const root = parts[0];
91
- if (isLocal(root))
92
- break;
93
- variables.add(root);
94
- break;
95
- }
96
- case 'Hash': {
97
- const pairs = n.pairs;
98
- if (pairs)
99
- for (const pair of pairs)
100
- visit(pair.value);
101
- break;
102
- }
103
- // ContentStatement, CommentStatement, *Literal, PartialStatement — no variables to extract.
179
+ // ContentStatement, CommentStatement, literals, partials — no variables.
104
180
  default:
105
181
  break;
106
182
  }
107
183
  };
108
184
  visit(ast);
109
- return variables;
185
+ return { references, helperMisuses };
186
+ }
187
+ /**
188
+ * Extract the set of root-level input variable names a Handlebars template reads.
189
+ *
190
+ * - `{{foo}}` → `foo`; `{{obj.bar.baz}}` → `obj`
191
+ * - `{{#each items as |item|}}{{item.name}}{{/each}}` → `items` (`item` is a block param)
192
+ * - `{{lookup obj key}}` → `obj`, `key` (helper name skipped)
193
+ * - `{{@index}}`, `{{this}}`, and bare names inside `#each`/`#with` → ignored
194
+ *
195
+ * Returns an empty set on parse failure.
196
+ */
197
+ export function extractHandlebarsVariables(template) {
198
+ const analysis = analyzeHandlebarsTemplate(template);
199
+ return new Set(analysis?.references.map((r) => r.name) ?? []);
110
200
  }
111
201
  //# sourceMappingURL=extract-vars.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"extract-vars.js","sourceRoot":"","sources":["../../src/prompts/extract-vars.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"extract-vars.js","sourceRoot":"","sources":["../../src/prompts/extract-vars.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  import type { JSONObject, JSONSchema, PromptSegment } from '@llumiverse/common';
2
2
  import { type PromptSegmentDef, type PromptTemplate, TemplateType } from '@vertesia/common';
3
+ import { type TemplateSystemContext } from '@vertesia/jst';
3
4
  export interface SegmentPreview {
4
5
  error?: Error;
5
6
  title: string;
@@ -9,21 +10,18 @@ export interface SegmentPreview {
9
10
  /**
10
11
  * Render a prompt template with the given input data.
11
12
  *
12
- * Handlebars templates use {{variable}} interpolation against `data`.
13
- * JST (JavaScript template) bodies evaluate against `data` with the schema's top-level
14
- * property names exposed as globals, plus `_model` — the active model id, which the
15
- * studio-server executor injects into the input as `{ ..._model: run.modelId }` when
16
- * executing an interaction (see `apps/studio-server/src/executor/ExecutionRequest.ts`
17
- * and `apps/studio-server/src/executor/rendering/template.ts`). Listing it here keeps
18
- * the Playground preview and `validatePrompt` in sync with runtime resolution — a JST
19
- * template referencing `_model` validates fine here and renders fine in production.
13
+ * Handlebars templates use {{variable}} interpolation against `data`. JST (JavaScript template)
14
+ * bodies evaluate against `data` with the schema's top-level property names exposed as globals.
15
+ * Both see the system variables (`TEMPLATE_SYSTEM_VARIABLES` in `@vertesia/jst`), added here the
16
+ * same way the studio-server executor adds them, so a preview renders what an execution would.
17
+ * `_model` is only set when the caller passes `system.model` or the data already carries it.
20
18
  *
21
19
  * For `TemplateType.text`, the content is returned verbatim — it is static text, not a
22
20
  * template — matching the studio-server executor (see `apps/studio-server/src/executor/
23
21
  * rendering/template.ts`). Routing it through the JST evaluator would compile the prose as
24
22
  * JavaScript and throw on any plain sentence (e.g. "You are a helpful assistant.").
25
23
  */
26
- export declare function renderTemplate(code: string, contentType: TemplateType, schema: JSONSchema, data: JSONObject): string;
24
+ export declare function renderTemplate(code: string, contentType: TemplateType, schema: JSONSchema, data: JSONObject, system?: TemplateSystemContext): string;
27
25
  /**
28
26
  * Execute a JST (JavaScript Template) with given data.
29
27
  * Returns a discriminated union to surface errors without throwing.