@happyvertical/smrt-core 0.55.10 → 0.55.11

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/AGENTS.md +2 -0
  2. package/agents/build-knowledge.md +40 -0
  3. package/agents/cookbook-engine.md +102 -0
  4. package/agents/cookbook.md +63 -0
  5. package/dist/consumer-plugin/artifact-publication.js +3 -3
  6. package/dist/consumer-plugin/artifact-publication.js.map +1 -1
  7. package/dist/consumer-plugin/hosted-sti-collections.d.ts +10 -0
  8. package/dist/consumer-plugin/hosted-sti-collections.d.ts.map +1 -0
  9. package/dist/consumer-plugin/hosted-sti-collections.js +92 -0
  10. package/dist/consumer-plugin/hosted-sti-collections.js.map +1 -0
  11. package/dist/consumer-plugin/index.d.ts +1 -1
  12. package/dist/consumer-plugin/index.d.ts.map +1 -1
  13. package/dist/consumer-plugin/index.js +76 -51
  14. package/dist/consumer-plugin/index.js.map +1 -1
  15. package/dist/cookbook/engine/commands.d.ts +37 -0
  16. package/dist/cookbook/engine/commands.d.ts.map +1 -0
  17. package/dist/cookbook/engine/commands.js +275 -0
  18. package/dist/cookbook/engine/commands.js.map +1 -0
  19. package/dist/cookbook/engine/defaults.d.ts +10 -0
  20. package/dist/cookbook/engine/defaults.d.ts.map +1 -0
  21. package/dist/cookbook/engine/defaults.js +49 -0
  22. package/dist/cookbook/engine/defaults.js.map +1 -0
  23. package/dist/cookbook/engine/document.d.ts +66 -0
  24. package/dist/cookbook/engine/document.d.ts.map +1 -0
  25. package/dist/cookbook/engine/document.js +357 -0
  26. package/dist/cookbook/engine/document.js.map +1 -0
  27. package/dist/cookbook/engine/engine.d.ts +49 -0
  28. package/dist/cookbook/engine/engine.d.ts.map +1 -0
  29. package/dist/cookbook/engine/engine.js +399 -0
  30. package/dist/cookbook/engine/engine.js.map +1 -0
  31. package/dist/cookbook/engine/index.d.ts +14 -0
  32. package/dist/cookbook/engine/index.d.ts.map +1 -0
  33. package/dist/cookbook/engine/index.js +8 -0
  34. package/dist/cookbook/engine/nav.d.ts +45 -0
  35. package/dist/cookbook/engine/nav.d.ts.map +1 -0
  36. package/dist/cookbook/engine/nav.js +191 -0
  37. package/dist/cookbook/engine/nav.js.map +1 -0
  38. package/dist/cookbook/engine/schemas.d.ts +29 -0
  39. package/dist/cookbook/engine/schemas.d.ts.map +1 -0
  40. package/dist/cookbook/engine/schemas.js +363 -0
  41. package/dist/cookbook/engine/schemas.js.map +1 -0
  42. package/dist/cookbook/engine/types.d.ts +331 -0
  43. package/dist/cookbook/engine/types.d.ts.map +1 -0
  44. package/dist/cookbook/engine/types.js +10 -0
  45. package/dist/cookbook/engine/types.js.map +1 -0
  46. package/dist/cookbook/engine.d.ts +2 -0
  47. package/dist/cookbook/engine.d.ts.map +1 -0
  48. package/dist/cookbook/engine.js +9 -0
  49. package/dist/cookbook/index.d.ts +6 -0
  50. package/dist/cookbook/index.d.ts.map +1 -0
  51. package/dist/cookbook/v1.schema.json +131 -0
  52. package/dist/cookbook/validate.d.ts +37 -0
  53. package/dist/cookbook/validate.d.ts.map +1 -0
  54. package/dist/cookbook/validate.js +172 -0
  55. package/dist/cookbook/validate.js.map +1 -0
  56. package/dist/cookbook.d.ts +7 -0
  57. package/dist/cookbook.d.ts.map +1 -0
  58. package/dist/cookbook.js +2 -0
  59. package/dist/manifest/static-manifest.js +1 -1
  60. package/dist/manifest/static-manifest.js.map +1 -1
  61. package/dist/manifest/store.js +1 -1
  62. package/dist/manifest.json +1 -1
  63. package/dist/prebuild/index.js +13 -13
  64. package/dist/prebuild/index.js.map +1 -1
  65. package/dist/smrt-knowledge.json +23 -7
  66. package/dist/vite-plugin/web-collections.d.ts +5 -0
  67. package/dist/vite-plugin/web-collections.d.ts.map +1 -1
  68. package/dist/vite-plugin/web-collections.js +1 -1
  69. package/dist/vite-plugin/web-collections.js.map +1 -1
  70. package/dist/vite-preset/cookbook-exposure.d.ts +11 -0
  71. package/dist/vite-preset/cookbook-exposure.d.ts.map +1 -0
  72. package/dist/vite-preset/cookbook-exposure.js +95 -0
  73. package/dist/vite-preset/cookbook-exposure.js.map +1 -0
  74. package/dist/vite-preset/index.d.ts +9 -0
  75. package/dist/vite-preset/index.d.ts.map +1 -1
  76. package/dist/vite-preset/index.js +17 -7
  77. package/dist/vite-preset/index.js.map +1 -1
  78. package/package.json +18 -6
@@ -0,0 +1,191 @@
1
+ //#region src/cookbook/engine/nav.ts
2
+ /** The section that holds feature models no recipe covers. */
3
+ var FEATURE_SECTION = {
4
+ id: "more",
5
+ label: "More"
6
+ };
7
+ var QUALIFIED = /^@[^/]+\/(?:smrt-)?([^:]+):(.+)$/;
8
+ /** Class name and package id of a qualified model name, from the catalog or the name itself. */
9
+ function modelParts(catalog, qualified) {
10
+ const known = catalog.models.find((m) => m.id === qualified);
11
+ if (known) return {
12
+ packageId: known.packageId,
13
+ name: known.name
14
+ };
15
+ const match = QUALIFIED.exec(qualified);
16
+ return match ? {
17
+ packageId: match[1],
18
+ name: match[2]
19
+ } : {
20
+ packageId: "unknown",
21
+ name: qualified
22
+ };
23
+ }
24
+ /** The stable layout id of a navigation item (same as smrt-svelte's `navItemId`). */
25
+ var navItemId = (packageId, modelName, key) => `item:${packageId}:${modelName}${key ? `:${key}` : ""}`;
26
+ /** `Purchase Order` from `PurchaseOrder`. */
27
+ var humanize = (name) => name.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/^./, (c) => c.toUpperCase());
28
+ var modelLabel = (catalog, id) => humanize(modelParts(catalog, id).name);
29
+ /** The menu a document generates, before its layout. */
30
+ function generated(catalog, doc) {
31
+ const byId = new Map(catalog.recipes.map((r) => [r.id, r]));
32
+ const sections = /* @__PURE__ */ new Map();
33
+ for (const recipeId of [...doc.recipes].sort()) {
34
+ const recipe = byId.get(recipeId);
35
+ if (!recipe) continue;
36
+ const suggested = recipe.section ?? {
37
+ id: recipe.group?.id ?? recipe.id,
38
+ label: recipe.group?.label ?? recipe.label
39
+ };
40
+ const id = `section:${suggested.id}`;
41
+ const section = sections.get(id) ?? {
42
+ id,
43
+ label: suggested.label,
44
+ items: []
45
+ };
46
+ for (const entry of recipe.nav) {
47
+ const { packageId, name } = modelParts(catalog, entry.model);
48
+ const itemId = navItemId(packageId, name, entry.key);
49
+ if (!section.items.some((i) => i.id === itemId)) section.items.push({
50
+ id: itemId,
51
+ label: entry.label
52
+ });
53
+ }
54
+ sections.set(id, section);
55
+ }
56
+ const out = [...sections.values()].filter((s) => s.items.length > 0);
57
+ const known = new Set(catalog.models.filter((m) => m.exposed !== false).map((m) => m.id));
58
+ const features = doc.features.filter((id) => known.has(id));
59
+ if (features.length) out.push({
60
+ id: `section:${FEATURE_SECTION.id}`,
61
+ label: FEATURE_SECTION.label,
62
+ items: features.map((id) => {
63
+ const { packageId, name } = modelParts(catalog, id);
64
+ return {
65
+ id: navItemId(packageId, name),
66
+ label: humanize(name)
67
+ };
68
+ })
69
+ });
70
+ return out;
71
+ }
72
+ var order = (list, wanted) => {
73
+ if (!wanted?.length) return list;
74
+ const rank = (id) => {
75
+ const i = wanted.indexOf(id);
76
+ return i < 0 ? Number.MAX_SAFE_INTEGER : i;
77
+ };
78
+ return [...list].sort((a, b) => rank(a.id) - rank(b.id));
79
+ };
80
+ /** The menu with the document's layout applied: moves, order, hides, renames. */
81
+ function buildNav(catalog, doc) {
82
+ const layout = doc.layout;
83
+ const sections = generated(catalog, doc).map((s) => ({
84
+ id: s.id,
85
+ label: s.label,
86
+ defaultLabel: s.label,
87
+ custom: false,
88
+ hidden: false,
89
+ items: s.items.map((i) => ({
90
+ id: i.id,
91
+ label: i.label,
92
+ defaultLabel: i.label,
93
+ hidden: false
94
+ }))
95
+ }));
96
+ for (const custom of layout?.customSections ?? []) {
97
+ if (sections.some((s) => s.id === custom.id)) continue;
98
+ sections.push({
99
+ id: custom.id,
100
+ label: custom.label,
101
+ defaultLabel: custom.label,
102
+ custom: true,
103
+ hidden: false,
104
+ items: []
105
+ });
106
+ }
107
+ for (const [itemId, target] of Object.entries(layout?.moved ?? {})) {
108
+ const to = sections.find((s) => s.id === target);
109
+ if (!to) continue;
110
+ for (const from of sections) {
111
+ const at = from.items.findIndex((i) => i.id === itemId);
112
+ if (at >= 0 && from !== to) to.items.push(...from.items.splice(at, 1));
113
+ }
114
+ }
115
+ const hidden = new Set(layout?.hidden ?? []);
116
+ for (const section of sections) {
117
+ const label = layout?.sections?.[section.id]?.label?.trim();
118
+ if (label && !section.custom) section.label = label;
119
+ section.hidden = hidden.has(section.id);
120
+ section.items = order(section.items, layout?.itemOrder?.[section.id]);
121
+ for (const item of section.items) {
122
+ const own = layout?.items?.[item.id]?.label?.trim();
123
+ if (own) item.label = own;
124
+ item.hidden = hidden.has(item.id);
125
+ }
126
+ }
127
+ return order(sections.filter((s) => s.custom || s.items.length > 0), layout?.sectionOrder);
128
+ }
129
+ /** Is this model known as exposed by the catalog? */
130
+ var findModel = (catalog, id) => catalog.models.find((m) => m.id === id);
131
+ var compact = (layout) => {
132
+ const out = { ...layout };
133
+ for (const key of [
134
+ "hidden",
135
+ "customSections",
136
+ "sectionOrder"
137
+ ]) if (!out[key]?.length) delete out[key];
138
+ for (const key of [
139
+ "sections",
140
+ "items",
141
+ "itemOrder",
142
+ "moved",
143
+ "panels",
144
+ "placements"
145
+ ]) if (out[key] && Object.keys(out[key]).length === 0) delete out[key];
146
+ return Object.keys(out).length > 1 ? out : void 0;
147
+ };
148
+ /** A section rename; blank or the default label removes the override. */
149
+ function withSectionRenamed(layout, section, label) {
150
+ const base = layout ? structuredClone(layout) : { version: 1 };
151
+ const clean = label.trim();
152
+ if (section.custom) {
153
+ if (!clean) return layout;
154
+ base.customSections = base.customSections?.map((c) => c.id === section.id ? {
155
+ ...c,
156
+ label: clean
157
+ } : c);
158
+ return compact(base);
159
+ }
160
+ const sections = { ...base.sections ?? {} };
161
+ const entry = { ...sections[section.id] ?? {} };
162
+ if (!clean || clean === section.defaultLabel) delete entry.label;
163
+ else entry.label = clean;
164
+ if (Object.keys(entry).length) sections[section.id] = entry;
165
+ else delete sections[section.id];
166
+ base.sections = sections;
167
+ return compact(base);
168
+ }
169
+ /** An item rename; `null`, blank or the default label removes the override. */
170
+ function withItemRenamed(layout, item, label) {
171
+ const base = layout ? structuredClone(layout) : { version: 1 };
172
+ const clean = label?.trim() ?? "";
173
+ const items = { ...base.items ?? {} };
174
+ const entry = { ...items[item.id] ?? {} };
175
+ if (!clean || clean === item.defaultLabel) delete entry.label;
176
+ else entry.label = clean;
177
+ if (Object.keys(entry).length) items[item.id] = entry;
178
+ else delete items[item.id];
179
+ base.items = items;
180
+ return compact(base);
181
+ }
182
+ function withHidden(layout, id, hide) {
183
+ const base = layout ? structuredClone(layout) : { version: 1 };
184
+ const hidden = (base.hidden ?? []).filter((h) => h !== id);
185
+ base.hidden = hide ? [...hidden, id] : hidden;
186
+ return compact(base);
187
+ }
188
+ //#endregion
189
+ export { FEATURE_SECTION, buildNav, findModel, humanize, modelLabel, modelParts, navItemId, withHidden, withItemRenamed, withSectionRenamed };
190
+
191
+ //# sourceMappingURL=nav.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nav.js","names":[],"sources":["../../../src/cookbook/engine/nav.ts"],"sourcesContent":["import type { Cookbook, CookbookLayout } from '@happyvertical/smrt-types';\nimport type { EngineCatalog, EngineModel } from './types.js';\n\n/** A menu entry after the layout is applied. */\nexport interface NavItem {\n id: string;\n label: string;\n defaultLabel: string;\n hidden: boolean;\n}\n\nexport interface NavSection {\n /** `section:<id>`, or the id of a custom section. */\n id: string;\n label: string;\n defaultLabel: string;\n custom: boolean;\n hidden: boolean;\n items: NavItem[];\n}\n\n/** The section that holds feature models no recipe covers. */\nexport const FEATURE_SECTION = { id: 'more', label: 'More' } as const;\n\nconst QUALIFIED = /^@[^/]+\\/(?:smrt-)?([^:]+):(.+)$/;\n\n/** Class name and package id of a qualified model name, from the catalog or the name itself. */\nexport function modelParts(\n catalog: EngineCatalog,\n qualified: string,\n): { packageId: string; name: string } {\n const known = catalog.models.find((m) => m.id === qualified);\n if (known) return { packageId: known.packageId, name: known.name };\n const match = QUALIFIED.exec(qualified);\n return match\n ? { packageId: match[1], name: match[2] }\n : { packageId: 'unknown', name: qualified };\n}\n\n/** The stable layout id of a navigation item (same as smrt-svelte's `navItemId`). */\nexport const navItemId = (\n packageId: string,\n modelName: string,\n key?: string,\n): string => `item:${packageId}:${modelName}${key ? `:${key}` : ''}`;\n\n/** `Purchase Order` from `PurchaseOrder`. */\nexport const humanize = (name: string): string =>\n name\n .replace(/([a-z0-9])([A-Z])/g, '$1 $2')\n .replace(/^./, (c) => c.toUpperCase());\n\nexport const modelLabel = (catalog: EngineCatalog, id: string): string =>\n humanize(modelParts(catalog, id).name);\n\ninterface RawSection {\n id: string;\n label: string;\n items: { id: string; label: string }[];\n}\n\n/** The menu a document generates, before its layout. */\nfunction generated(\n catalog: EngineCatalog,\n doc: Pick<Cookbook, 'recipes' | 'features'>,\n): RawSection[] {\n const byId = new Map(catalog.recipes.map((r) => [r.id, r]));\n const sections = new Map<string, RawSection>();\n for (const recipeId of [...doc.recipes].sort()) {\n const recipe = byId.get(recipeId);\n if (!recipe) continue;\n const suggested = recipe.section ?? {\n id: recipe.group?.id ?? recipe.id,\n label: recipe.group?.label ?? recipe.label,\n };\n const id = `section:${suggested.id}`;\n const section = sections.get(id) ?? {\n id,\n label: suggested.label,\n items: [],\n };\n for (const entry of recipe.nav) {\n const { packageId, name } = modelParts(catalog, entry.model);\n const itemId = navItemId(packageId, name, entry.key);\n if (!section.items.some((i) => i.id === itemId)) {\n section.items.push({ id: itemId, label: entry.label });\n }\n }\n sections.set(id, section);\n }\n const out = [...sections.values()].filter((s) => s.items.length > 0);\n const known = new Set(\n catalog.models.filter((m) => m.exposed !== false).map((m) => m.id),\n );\n const features = doc.features.filter((id) => known.has(id));\n if (features.length) {\n out.push({\n id: `section:${FEATURE_SECTION.id}`,\n label: FEATURE_SECTION.label,\n items: features.map((id) => {\n const { packageId, name } = modelParts(catalog, id);\n return { id: navItemId(packageId, name), label: humanize(name) };\n }),\n });\n }\n return out;\n}\n\nconst order = <T extends { id: string }>(\n list: T[],\n wanted: readonly string[] | undefined,\n): T[] => {\n if (!wanted?.length) return list;\n const rank = (id: string) => {\n const i = wanted.indexOf(id);\n return i < 0 ? Number.MAX_SAFE_INTEGER : i;\n };\n return [...list].sort((a, b) => rank(a.id) - rank(b.id));\n};\n\n/** The menu with the document's layout applied: moves, order, hides, renames. */\nexport function buildNav(\n catalog: EngineCatalog,\n doc: Pick<Cookbook, 'recipes' | 'features'> & { layout?: CookbookLayout },\n): NavSection[] {\n const layout = doc.layout;\n const raw = generated(catalog, doc);\n const sections: NavSection[] = raw.map((s) => ({\n id: s.id,\n label: s.label,\n defaultLabel: s.label,\n custom: false,\n hidden: false,\n items: s.items.map((i) => ({\n id: i.id,\n label: i.label,\n defaultLabel: i.label,\n hidden: false,\n })),\n }));\n for (const custom of layout?.customSections ?? []) {\n if (sections.some((s) => s.id === custom.id)) continue;\n sections.push({\n id: custom.id,\n label: custom.label,\n defaultLabel: custom.label,\n custom: true,\n hidden: false,\n items: [],\n });\n }\n for (const [itemId, target] of Object.entries(layout?.moved ?? {})) {\n const to = sections.find((s) => s.id === target);\n if (!to) continue;\n for (const from of sections) {\n const at = from.items.findIndex((i) => i.id === itemId);\n if (at >= 0 && from !== to) to.items.push(...from.items.splice(at, 1));\n }\n }\n const hidden = new Set(layout?.hidden ?? []);\n for (const section of sections) {\n const label = layout?.sections?.[section.id]?.label?.trim();\n if (label && !section.custom) section.label = label;\n section.hidden = hidden.has(section.id);\n section.items = order(section.items, layout?.itemOrder?.[section.id]);\n for (const item of section.items) {\n const own = layout?.items?.[item.id]?.label?.trim();\n if (own) item.label = own;\n item.hidden = hidden.has(item.id);\n }\n }\n return order(\n sections.filter((s) => s.custom || s.items.length > 0),\n layout?.sectionOrder,\n );\n}\n\n/** Is this model known as exposed by the catalog? */\nexport const findModel = (\n catalog: EngineCatalog,\n id: string,\n): EngineModel | undefined => catalog.models.find((m) => m.id === id);\n\nconst compact = (layout: CookbookLayout): CookbookLayout | undefined => {\n const out: CookbookLayout = { ...layout };\n for (const key of ['hidden', 'customSections', 'sectionOrder'] as const) {\n if (!out[key]?.length) delete out[key];\n }\n for (const key of [\n 'sections',\n 'items',\n 'itemOrder',\n 'moved',\n 'panels',\n 'placements',\n ] as const) {\n if (out[key] && Object.keys(out[key] as object).length === 0)\n delete out[key];\n }\n return Object.keys(out).length > 1 ? out : undefined;\n};\n\n/** A section rename; blank or the default label removes the override. */\nexport function withSectionRenamed(\n layout: CookbookLayout | undefined,\n section: NavSection,\n label: string,\n): CookbookLayout | undefined {\n const base: CookbookLayout = layout\n ? structuredClone(layout)\n : { version: 1 };\n const clean = label.trim();\n if (section.custom) {\n if (!clean) return layout;\n base.customSections = base.customSections?.map((c) =>\n c.id === section.id ? { ...c, label: clean } : c,\n );\n return compact(base);\n }\n const sections = { ...(base.sections ?? {}) };\n const entry = { ...(sections[section.id] ?? {}) };\n if (!clean || clean === section.defaultLabel) delete entry.label;\n else entry.label = clean;\n if (Object.keys(entry).length) sections[section.id] = entry;\n else delete sections[section.id];\n base.sections = sections;\n return compact(base);\n}\n\n/** An item rename; `null`, blank or the default label removes the override. */\nexport function withItemRenamed(\n layout: CookbookLayout | undefined,\n item: NavItem,\n label: string | null,\n): CookbookLayout | undefined {\n const base: CookbookLayout = layout\n ? structuredClone(layout)\n : { version: 1 };\n const clean = label?.trim() ?? '';\n const items = { ...(base.items ?? {}) };\n const entry = { ...(items[item.id] ?? {}) };\n if (!clean || clean === item.defaultLabel) delete entry.label;\n else entry.label = clean;\n if (Object.keys(entry).length) items[item.id] = entry;\n else delete items[item.id];\n base.items = items;\n return compact(base);\n}\n\nexport function withHidden(\n layout: CookbookLayout | undefined,\n id: string,\n hide: boolean,\n): CookbookLayout | undefined {\n const base: CookbookLayout = layout\n ? structuredClone(layout)\n : { version: 1 };\n const hidden = (base.hidden ?? []).filter((h) => h !== id);\n base.hidden = hide ? [...hidden, id] : hidden;\n return compact(base);\n}\n"],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ import type { CommandName, CommandTool, EngineCatalog, JsonSchema } from './types.js';
2
+ /**
3
+ * Every command as an MCP/LLM tool definition (`inputSchema` is JSON Schema
4
+ * 2020-12). Recipe and cookbook ids become enums from the catalog, so
5
+ * grammar-constrained decoding cannot name one that does not exist.
6
+ */
7
+ export declare function buildCommandTools(catalog: EngineCatalog): CommandTool[];
8
+ /**
9
+ * The batch envelope as a tool definition: several commands, applied
10
+ * all-or-nothing. Commands inside are validated against their own schemas.
11
+ */
12
+ export declare function buildBatchTool(catalog: EngineCatalog): {
13
+ name: 'run_batch';
14
+ description: string;
15
+ inputSchema: JsonSchema;
16
+ };
17
+ /**
18
+ * Check a value against a command schema (the subset the commands use). One
19
+ * validator for runtime and exported schemas, so they cannot drift. Returns
20
+ * the first problem as `{ path, message }`, or null.
21
+ */
22
+ export declare function checkSchema(schema: JsonSchema, value: unknown, path?: string): {
23
+ path: string;
24
+ message: string;
25
+ } | null;
26
+ export type CommandSchemas = Readonly<Record<CommandName, JsonSchema>>;
27
+ /** The input schemas by command name. */
28
+ export declare function commandSchemas(catalog: EngineCatalog): CommandSchemas;
29
+ //# sourceMappingURL=schemas.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../../../src/cookbook/engine/schemas.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,WAAW,EACX,WAAW,EACX,aAAa,EACb,UAAU,EACX,MAAM,YAAY,CAAC;AA8BpB;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,aAAa,GAAG,WAAW,EAAE,CAmPvE;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,aAAa,GAAG;IACtD,IAAI,EAAE,WAAW,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,UAAU,CAAC;CACzB,CA+BA;AAgBD;;;;GAIG;AACH,wBAAgB,WAAW,CACzB,MAAM,EAAE,UAAU,EAClB,KAAK,EAAE,OAAO,EACd,IAAI,SAAU,GACb;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAiG1C;AAED,MAAM,MAAM,cAAc,GAAG,QAAQ,CAAC,MAAM,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC,CAAC;AAEvE,yCAAyC;AACzC,wBAAgB,cAAc,CAAC,OAAO,EAAE,aAAa,GAAG,cAAc,CAIrE"}
@@ -0,0 +1,363 @@
1
+ import { DEFAULT_THEME_PRESETS } from "./defaults.js";
2
+ //#region src/cookbook/engine/schemas.ts
3
+ var ids = (description, values) => ({
4
+ type: "array",
5
+ description,
6
+ minItems: 1,
7
+ maxItems: 50,
8
+ items: values?.length ? {
9
+ type: "string",
10
+ enum: [...values]
11
+ } : {
12
+ type: "string",
13
+ minLength: 1
14
+ }
15
+ });
16
+ var text = (description, extra = {}) => ({
17
+ type: "string",
18
+ description,
19
+ minLength: 1,
20
+ maxLength: 200,
21
+ ...extra
22
+ });
23
+ var object = (properties, required = []) => ({
24
+ type: "object",
25
+ properties,
26
+ ...required.length ? { required } : {},
27
+ additionalProperties: false
28
+ });
29
+ /**
30
+ * Every command as an MCP/LLM tool definition (`inputSchema` is JSON Schema
31
+ * 2020-12). Recipe and cookbook ids become enums from the catalog, so
32
+ * grammar-constrained decoding cannot name one that does not exist.
33
+ */
34
+ function buildCommandTools(catalog) {
35
+ const recipeIds = catalog.recipes.map((r) => r.id);
36
+ const cookbookIds = (catalog.cookbooks ?? []).map((c) => c.id);
37
+ const presets = catalog.themePresets ?? DEFAULT_THEME_PRESETS;
38
+ const cookbookId = (description) => ({
39
+ type: "string",
40
+ description,
41
+ ...cookbookIds.length ? { enum: [...cookbookIds] } : { minLength: 1 }
42
+ });
43
+ const entryId = (what) => text(`The id of ${what}, as the plan snapshot's "sections" lists it.`);
44
+ const replace = {
45
+ type: "boolean",
46
+ description: "Confirm replacing what is there."
47
+ };
48
+ return [
49
+ {
50
+ name: "add_recipes",
51
+ description: "Switch recipes on. Recipes they require come along. Returns what was added.",
52
+ inputSchema: object({ ids: ids("Recipe ids to add.", recipeIds) }, ["ids"])
53
+ },
54
+ {
55
+ name: "remove_recipes",
56
+ description: "Switch recipes off. A recipe another one needs is kept and reported.",
57
+ inputSchema: object({ ids: ids("Recipe ids to remove.", recipeIds) }, ["ids"])
58
+ },
59
+ {
60
+ name: "add_features",
61
+ description: "Add single models no recipe covers, by qualified name (`@scope/pkg:Class`).",
62
+ inputSchema: object({ ids: ids("Qualified model names to add.") }, ["ids"])
63
+ },
64
+ {
65
+ name: "remove_features",
66
+ description: "Remove single models added with add_features.",
67
+ inputSchema: object({ ids: ids("Qualified model names to remove.") }, ["ids"])
68
+ },
69
+ {
70
+ name: "add_cookbook",
71
+ description: "Add the recipes and features of a ready-made cookbook to the current app. Nothing is replaced.",
72
+ inputSchema: object({ id: cookbookId("The cookbook to add.") }, ["id"])
73
+ },
74
+ {
75
+ name: "remove_cookbook",
76
+ description: "Remove the recipes and features a ready-made cookbook adds. Ones another recipe needs are kept.",
77
+ inputSchema: object({ id: cookbookId("The cookbook to remove.") }, ["id"])
78
+ },
79
+ {
80
+ name: "apply_cookbook",
81
+ description: "Select a ready-made cookbook: replace the whole app (recipes, options, menu, theme) with it. When the app has anything in it, replace must be true.",
82
+ inputSchema: object({
83
+ id: cookbookId("The cookbook to set up."),
84
+ replace
85
+ }, ["id"])
86
+ },
87
+ {
88
+ name: "import_cookbook",
89
+ description: "Replace the whole app with a cookbook document (version 1). Checked strictly; nothing is applied if it is invalid. When the app has anything in it, replace must be true.",
90
+ inputSchema: object({
91
+ document: {
92
+ type: "object",
93
+ description: "A cookbook document, version 1."
94
+ },
95
+ replace
96
+ }, ["document"])
97
+ },
98
+ {
99
+ name: "set_settings",
100
+ description: "Change app settings: currency, default tax rate (percent), default payment terms. Applies to new records only.",
101
+ inputSchema: object({
102
+ currency: catalog.currencies?.length ? {
103
+ type: "string",
104
+ description: "ISO 4217 code, e.g. CAD.",
105
+ enum: [...catalog.currencies]
106
+ } : {
107
+ type: "string",
108
+ description: "ISO 4217 code, e.g. CAD.",
109
+ pattern: "^[A-Z]{3}$"
110
+ },
111
+ taxRate: {
112
+ type: "number",
113
+ description: "Default tax rate in percent, 0 to 100.",
114
+ minimum: 0,
115
+ maximum: 100
116
+ },
117
+ paymentTerms: {
118
+ type: "string",
119
+ description: "Default payment terms, e.g. \"Net 30\".",
120
+ minLength: 1,
121
+ maxLength: 40
122
+ }
123
+ })
124
+ },
125
+ {
126
+ name: "set_policy",
127
+ description: "Set one field's policy: default value, visibility, label or help. The model must be covered by a recipe or feature that is on. null clears a part.",
128
+ inputSchema: object({
129
+ model: text("Qualified model name, e.g. @happyvertical/smrt-commerce:Order."),
130
+ field: text("The model field name."),
131
+ defaultValue: { description: "The default as a JSON value (a string, number or boolean). null clears it." },
132
+ visibility: {
133
+ enum: [
134
+ "basic",
135
+ "advanced",
136
+ "hidden",
137
+ null
138
+ ],
139
+ description: "Whether the field shows in forms and lists."
140
+ },
141
+ label: {
142
+ type: ["string", "null"],
143
+ description: "A different label.",
144
+ maxLength: 120
145
+ },
146
+ help: {
147
+ type: ["string", "null"],
148
+ description: "Help text under the field.",
149
+ maxLength: 400
150
+ }
151
+ }, ["model", "field"])
152
+ },
153
+ {
154
+ name: "set_theme",
155
+ description: "Change the look: a preset, a brand colour, light or dark. A preset replaces a brand colour.",
156
+ inputSchema: object({
157
+ preset: {
158
+ enum: [...presets],
159
+ description: "A built-in theme."
160
+ },
161
+ primary: {
162
+ type: "string",
163
+ description: "Brand colour as #rrggbb.",
164
+ pattern: "^#[0-9a-fA-F]{6}$"
165
+ },
166
+ colorScheme: {
167
+ enum: [
168
+ "light",
169
+ "dark",
170
+ "system"
171
+ ],
172
+ description: "Light, dark or follow the device."
173
+ }
174
+ })
175
+ },
176
+ {
177
+ name: "reset_theme",
178
+ description: "Go back to the default look.",
179
+ inputSchema: object({})
180
+ },
181
+ {
182
+ name: "set_name",
183
+ description: "Name and describe the app. null clears a part.",
184
+ inputSchema: object({
185
+ name: {
186
+ type: ["string", "null"],
187
+ minLength: 1,
188
+ maxLength: 60
189
+ },
190
+ description: {
191
+ type: ["string", "null"],
192
+ minLength: 1,
193
+ maxLength: 400
194
+ }
195
+ })
196
+ },
197
+ {
198
+ name: "rename_section",
199
+ description: "Rename a menu section.",
200
+ inputSchema: object({
201
+ id: entryId("the section"),
202
+ label: text("The new name.", { maxLength: 60 })
203
+ }, ["id", "label"])
204
+ },
205
+ {
206
+ name: "rename_item",
207
+ description: "Rename a menu entry. A null label restores its own name.",
208
+ inputSchema: object({
209
+ id: entryId("the entry"),
210
+ label: {
211
+ type: ["string", "null"],
212
+ minLength: 1,
213
+ maxLength: 60
214
+ }
215
+ }, ["id", "label"])
216
+ },
217
+ {
218
+ name: "hide",
219
+ description: "Hide a menu section or entry. Nothing is deleted.",
220
+ inputSchema: object({ id: entryId("the section or entry") }, ["id"])
221
+ },
222
+ {
223
+ name: "show",
224
+ description: "Show a hidden menu section or entry again.",
225
+ inputSchema: object({ id: entryId("the section or entry") }, ["id"])
226
+ },
227
+ {
228
+ name: "validate",
229
+ description: "Check the current cookbook against the catalog and report typed diagnostics. Changes nothing.",
230
+ inputSchema: object({})
231
+ },
232
+ {
233
+ name: "export_cookbook",
234
+ description: "The cookbook as a <name>.cookbook.json file (text in the result). Changes nothing.",
235
+ inputSchema: object({ name: text("A name for the file.", { maxLength: 60 }) })
236
+ },
237
+ {
238
+ name: "undo",
239
+ description: "Undo an earlier change by the undoId its receipt carried. Refused with a conflict when the plan changed since, unless force is true.",
240
+ inputSchema: object({
241
+ undoId: text("The undoId from a receipt."),
242
+ force: {
243
+ type: "boolean",
244
+ description: "Undo even after later edits."
245
+ }
246
+ }, ["undoId"])
247
+ }
248
+ ];
249
+ }
250
+ /**
251
+ * The batch envelope as a tool definition: several commands, applied
252
+ * all-or-nothing. Commands inside are validated against their own schemas.
253
+ */
254
+ function buildBatchTool(catalog) {
255
+ return {
256
+ name: "run_batch",
257
+ description: "Run several commands as one atomic batch: all apply or none does. A failure names the command and leaves the plan unchanged.",
258
+ inputSchema: {
259
+ type: "object",
260
+ properties: {
261
+ id: {
262
+ type: "string",
263
+ minLength: 1,
264
+ maxLength: 200
265
+ },
266
+ expectedRevision: {
267
+ type: "integer",
268
+ minimum: 0
269
+ },
270
+ commands: {
271
+ type: "array",
272
+ minItems: 1,
273
+ maxItems: 50,
274
+ items: { oneOf: buildCommandTools(catalog).map((tool) => ({
275
+ type: "object",
276
+ properties: {
277
+ name: { const: tool.name },
278
+ input: tool.inputSchema
279
+ },
280
+ required: ["name"],
281
+ additionalProperties: false
282
+ })) }
283
+ }
284
+ },
285
+ required: ["commands"],
286
+ additionalProperties: false
287
+ }
288
+ };
289
+ }
290
+ var typeOf = (value) => value === null ? "null" : Array.isArray(value) ? "array" : Number.isInteger(value) ? "integer" : typeof value;
291
+ function typeMatches(wanted, value) {
292
+ const actual = typeOf(value);
293
+ return actual === wanted || wanted === "number" && actual === "integer";
294
+ }
295
+ /**
296
+ * Check a value against a command schema (the subset the commands use). One
297
+ * validator for runtime and exported schemas, so they cannot drift. Returns
298
+ * the first problem as `{ path, message }`, or null.
299
+ */
300
+ function checkSchema(schema, value, path = "input") {
301
+ const bad = (message) => ({
302
+ path,
303
+ message: `${path} ${message}`
304
+ });
305
+ const type = schema.type;
306
+ if (type) {
307
+ const wanted = Array.isArray(type) ? type : [type];
308
+ if (!wanted.some((t) => typeMatches(t, value))) return bad(`must be ${wanted.join(" or ")}`);
309
+ }
310
+ const choices = schema.enum;
311
+ if (choices && !choices.includes(value)) return bad(choices.length > 12 ? "is not a known value" : `must be one of ${choices.map((c) => JSON.stringify(c)).join(", ")}`);
312
+ if (typeof value === "string") {
313
+ const { minLength, maxLength, pattern } = schema;
314
+ if (minLength !== void 0 && value.length < minLength) return bad("must not be empty");
315
+ if (maxLength !== void 0 && value.length > maxLength) return bad(`must be at most ${maxLength} characters`);
316
+ if (pattern && !new RegExp(pattern).test(value)) return bad("has the wrong format");
317
+ }
318
+ if (typeof value === "number") {
319
+ const { minimum, maximum } = schema;
320
+ if (!Number.isFinite(value)) return bad("must be a finite number");
321
+ if (minimum !== void 0 && value < minimum) return bad(`must be at least ${minimum}`);
322
+ if (maximum !== void 0 && value > maximum) return bad(`must be at most ${maximum}`);
323
+ }
324
+ if (Array.isArray(value)) {
325
+ const { minItems, maxItems, items } = schema;
326
+ if (minItems !== void 0 && value.length < minItems) return bad(`needs at least ${minItems} item${minItems === 1 ? "" : "s"}`);
327
+ if (maxItems !== void 0 && value.length > maxItems) return bad(`takes at most ${maxItems} items`);
328
+ if (items) for (const [index, item] of value.entries()) {
329
+ const problem = checkSchema(items, item, `${path}[${index}]`);
330
+ if (problem) return problem;
331
+ }
332
+ }
333
+ if (typeOf(value) === "object") {
334
+ const object = value;
335
+ const { properties, required, additionalProperties } = schema;
336
+ for (const key of required ?? []) if (object[key] === void 0) return {
337
+ path: `${path}.${key}`,
338
+ message: `${path}.${key} is required`
339
+ };
340
+ for (const [key, entry] of Object.entries(object)) {
341
+ const child = properties?.[key];
342
+ if (!child) {
343
+ if (additionalProperties === false) return {
344
+ path: `${path}.${key}`,
345
+ message: `${path}.${key} is not an option of this command`
346
+ };
347
+ continue;
348
+ }
349
+ if (entry === void 0) continue;
350
+ const problem = checkSchema(child, entry, `${path}.${key}`);
351
+ if (problem) return problem;
352
+ }
353
+ }
354
+ return null;
355
+ }
356
+ /** The input schemas by command name. */
357
+ function commandSchemas(catalog) {
358
+ return Object.fromEntries(buildCommandTools(catalog).map((tool) => [tool.name, tool.inputSchema]));
359
+ }
360
+ //#endregion
361
+ export { buildBatchTool, buildCommandTools, checkSchema, commandSchemas };
362
+
363
+ //# sourceMappingURL=schemas.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schemas.js","names":[],"sources":["../../../src/cookbook/engine/schemas.ts"],"sourcesContent":["import { DEFAULT_THEME_PRESETS, MAX_TERMS_LENGTH } from './defaults.js';\nimport type {\n CommandName,\n CommandTool,\n EngineCatalog,\n JsonSchema,\n} from './types.js';\n\nconst ids = (description: string, values?: readonly string[]): JsonSchema => ({\n type: 'array',\n description,\n minItems: 1,\n maxItems: 50,\n items: values?.length\n ? { type: 'string', enum: [...values] }\n : { type: 'string', minLength: 1 },\n});\n\nconst text = (description: string, extra: JsonSchema = {}): JsonSchema => ({\n type: 'string',\n description,\n minLength: 1,\n maxLength: 200,\n ...extra,\n});\n\nconst object = (\n properties: Record<string, JsonSchema>,\n required: string[] = [],\n): JsonSchema => ({\n type: 'object',\n properties,\n ...(required.length ? { required } : {}),\n additionalProperties: false,\n});\n\n/**\n * Every command as an MCP/LLM tool definition (`inputSchema` is JSON Schema\n * 2020-12). Recipe and cookbook ids become enums from the catalog, so\n * grammar-constrained decoding cannot name one that does not exist.\n */\nexport function buildCommandTools(catalog: EngineCatalog): CommandTool[] {\n const recipeIds = catalog.recipes.map((r) => r.id);\n const cookbookIds = (catalog.cookbooks ?? []).map((c) => c.id);\n const presets = catalog.themePresets ?? DEFAULT_THEME_PRESETS;\n const cookbookId = (description: string): JsonSchema => ({\n type: 'string',\n description,\n ...(cookbookIds.length ? { enum: [...cookbookIds] } : { minLength: 1 }),\n });\n const entryId = (what: string): JsonSchema =>\n text(`The id of ${what}, as the plan snapshot's \"sections\" lists it.`);\n const replace: JsonSchema = {\n type: 'boolean',\n description: 'Confirm replacing what is there.',\n };\n return [\n {\n name: 'add_recipes',\n description:\n 'Switch recipes on. Recipes they require come along. Returns what was added.',\n inputSchema: object({ ids: ids('Recipe ids to add.', recipeIds) }, [\n 'ids',\n ]),\n },\n {\n name: 'remove_recipes',\n description:\n 'Switch recipes off. A recipe another one needs is kept and reported.',\n inputSchema: object({ ids: ids('Recipe ids to remove.', recipeIds) }, [\n 'ids',\n ]),\n },\n {\n name: 'add_features',\n description:\n 'Add single models no recipe covers, by qualified name (`@scope/pkg:Class`).',\n inputSchema: object({ ids: ids('Qualified model names to add.') }, [\n 'ids',\n ]),\n },\n {\n name: 'remove_features',\n description: 'Remove single models added with add_features.',\n inputSchema: object({ ids: ids('Qualified model names to remove.') }, [\n 'ids',\n ]),\n },\n {\n name: 'add_cookbook',\n description:\n 'Add the recipes and features of a ready-made cookbook to the current app. Nothing is replaced.',\n inputSchema: object({ id: cookbookId('The cookbook to add.') }, ['id']),\n },\n {\n name: 'remove_cookbook',\n description:\n 'Remove the recipes and features a ready-made cookbook adds. Ones another recipe needs are kept.',\n inputSchema: object({ id: cookbookId('The cookbook to remove.') }, [\n 'id',\n ]),\n },\n {\n name: 'apply_cookbook',\n description:\n 'Select a ready-made cookbook: replace the whole app (recipes, options, menu, theme) with it. When the app has anything in it, replace must be true.',\n inputSchema: object(\n { id: cookbookId('The cookbook to set up.'), replace },\n ['id'],\n ),\n },\n {\n name: 'import_cookbook',\n description:\n 'Replace the whole app with a cookbook document (version 1). Checked strictly; nothing is applied if it is invalid. When the app has anything in it, replace must be true.',\n inputSchema: object(\n {\n document: {\n type: 'object',\n description: 'A cookbook document, version 1.',\n },\n replace,\n },\n ['document'],\n ),\n },\n {\n name: 'set_settings',\n description:\n 'Change app settings: currency, default tax rate (percent), default payment terms. Applies to new records only.',\n inputSchema: object({\n currency: catalog.currencies?.length\n ? {\n type: 'string',\n description: 'ISO 4217 code, e.g. CAD.',\n enum: [...catalog.currencies],\n }\n : {\n type: 'string',\n description: 'ISO 4217 code, e.g. CAD.',\n pattern: '^[A-Z]{3}$',\n },\n taxRate: {\n type: 'number',\n description: 'Default tax rate in percent, 0 to 100.',\n minimum: 0,\n maximum: 100,\n },\n paymentTerms: {\n type: 'string',\n description: 'Default payment terms, e.g. \"Net 30\".',\n minLength: 1,\n maxLength: MAX_TERMS_LENGTH,\n },\n }),\n },\n {\n name: 'set_policy',\n description:\n \"Set one field's policy: default value, visibility, label or help. The model must be covered by a recipe or feature that is on. null clears a part.\",\n inputSchema: object(\n {\n model: text(\n 'Qualified model name, e.g. @happyvertical/smrt-commerce:Order.',\n ),\n field: text('The model field name.'),\n defaultValue: {\n description:\n 'The default as a JSON value (a string, number or boolean). null clears it.',\n },\n visibility: {\n enum: ['basic', 'advanced', 'hidden', null],\n description: 'Whether the field shows in forms and lists.',\n },\n label: {\n type: ['string', 'null'],\n description: 'A different label.',\n maxLength: 120,\n },\n help: {\n type: ['string', 'null'],\n description: 'Help text under the field.',\n maxLength: 400,\n },\n },\n ['model', 'field'],\n ),\n },\n {\n name: 'set_theme',\n description:\n 'Change the look: a preset, a brand colour, light or dark. A preset replaces a brand colour.',\n inputSchema: object({\n preset: { enum: [...presets], description: 'A built-in theme.' },\n primary: {\n type: 'string',\n description: 'Brand colour as #rrggbb.',\n pattern: '^#[0-9a-fA-F]{6}$',\n },\n colorScheme: {\n enum: ['light', 'dark', 'system'],\n description: 'Light, dark or follow the device.',\n },\n }),\n },\n {\n name: 'reset_theme',\n description: 'Go back to the default look.',\n inputSchema: object({}),\n },\n {\n name: 'set_name',\n description: 'Name and describe the app. null clears a part.',\n inputSchema: object({\n name: { type: ['string', 'null'], minLength: 1, maxLength: 60 },\n description: {\n type: ['string', 'null'],\n minLength: 1,\n maxLength: 400,\n },\n }),\n },\n {\n name: 'rename_section',\n description: 'Rename a menu section.',\n inputSchema: object(\n {\n id: entryId('the section'),\n label: text('The new name.', { maxLength: 60 }),\n },\n ['id', 'label'],\n ),\n },\n {\n name: 'rename_item',\n description: 'Rename a menu entry. A null label restores its own name.',\n inputSchema: object(\n {\n id: entryId('the entry'),\n label: { type: ['string', 'null'], minLength: 1, maxLength: 60 },\n },\n ['id', 'label'],\n ),\n },\n {\n name: 'hide',\n description: 'Hide a menu section or entry. Nothing is deleted.',\n inputSchema: object({ id: entryId('the section or entry') }, ['id']),\n },\n {\n name: 'show',\n description: 'Show a hidden menu section or entry again.',\n inputSchema: object({ id: entryId('the section or entry') }, ['id']),\n },\n {\n name: 'validate',\n description:\n 'Check the current cookbook against the catalog and report typed diagnostics. Changes nothing.',\n inputSchema: object({}),\n },\n {\n name: 'export_cookbook',\n description:\n 'The cookbook as a <name>.cookbook.json file (text in the result). Changes nothing.',\n inputSchema: object({\n name: text('A name for the file.', { maxLength: 60 }),\n }),\n },\n {\n name: 'undo',\n description:\n 'Undo an earlier change by the undoId its receipt carried. Refused with a conflict when the plan changed since, unless force is true.',\n inputSchema: object(\n {\n undoId: text('The undoId from a receipt.'),\n force: {\n type: 'boolean',\n description: 'Undo even after later edits.',\n },\n },\n ['undoId'],\n ),\n },\n ];\n}\n\n/**\n * The batch envelope as a tool definition: several commands, applied\n * all-or-nothing. Commands inside are validated against their own schemas.\n */\nexport function buildBatchTool(catalog: EngineCatalog): {\n name: 'run_batch';\n description: string;\n inputSchema: JsonSchema;\n} {\n return {\n name: 'run_batch',\n description:\n 'Run several commands as one atomic batch: all apply or none does. A failure names the command and leaves the plan unchanged.',\n inputSchema: {\n type: 'object',\n properties: {\n id: { type: 'string', minLength: 1, maxLength: 200 },\n expectedRevision: { type: 'integer', minimum: 0 },\n commands: {\n type: 'array',\n minItems: 1,\n maxItems: 50,\n items: {\n oneOf: buildCommandTools(catalog).map((tool) => ({\n type: 'object',\n properties: {\n name: { const: tool.name },\n input: tool.inputSchema,\n },\n required: ['name'],\n additionalProperties: false,\n })),\n },\n },\n },\n required: ['commands'],\n additionalProperties: false,\n },\n };\n}\n\nconst typeOf = (value: unknown): string =>\n value === null\n ? 'null'\n : Array.isArray(value)\n ? 'array'\n : Number.isInteger(value)\n ? 'integer'\n : typeof value;\n\nfunction typeMatches(wanted: string, value: unknown): boolean {\n const actual = typeOf(value);\n return actual === wanted || (wanted === 'number' && actual === 'integer');\n}\n\n/**\n * Check a value against a command schema (the subset the commands use). One\n * validator for runtime and exported schemas, so they cannot drift. Returns\n * the first problem as `{ path, message }`, or null.\n */\nexport function checkSchema(\n schema: JsonSchema,\n value: unknown,\n path = 'input',\n): { path: string; message: string } | null {\n const bad = (message: string) => ({ path, message: `${path} ${message}` });\n const type = schema.type as string | string[] | undefined;\n if (type) {\n const wanted = Array.isArray(type) ? type : [type];\n if (!wanted.some((t) => typeMatches(t, value))) {\n return bad(`must be ${wanted.join(' or ')}`);\n }\n }\n const choices = schema.enum as unknown[] | undefined;\n if (choices && !choices.includes(value)) {\n return bad(\n choices.length > 12\n ? 'is not a known value'\n : `must be one of ${choices.map((c) => JSON.stringify(c)).join(', ')}`,\n );\n }\n if (typeof value === 'string') {\n const { minLength, maxLength, pattern } = schema as {\n minLength?: number;\n maxLength?: number;\n pattern?: string;\n };\n if (minLength !== undefined && value.length < minLength) {\n return bad('must not be empty');\n }\n if (maxLength !== undefined && value.length > maxLength) {\n return bad(`must be at most ${maxLength} characters`);\n }\n if (pattern && !new RegExp(pattern).test(value)) {\n return bad('has the wrong format');\n }\n }\n if (typeof value === 'number') {\n const { minimum, maximum } = schema as {\n minimum?: number;\n maximum?: number;\n };\n if (!Number.isFinite(value)) return bad('must be a finite number');\n if (minimum !== undefined && value < minimum) {\n return bad(`must be at least ${minimum}`);\n }\n if (maximum !== undefined && value > maximum) {\n return bad(`must be at most ${maximum}`);\n }\n }\n if (Array.isArray(value)) {\n const { minItems, maxItems, items } = schema as {\n minItems?: number;\n maxItems?: number;\n items?: JsonSchema;\n };\n if (minItems !== undefined && value.length < minItems) {\n return bad(`needs at least ${minItems} item${minItems === 1 ? '' : 's'}`);\n }\n if (maxItems !== undefined && value.length > maxItems) {\n return bad(`takes at most ${maxItems} items`);\n }\n if (items) {\n for (const [index, item] of value.entries()) {\n const problem = checkSchema(items, item, `${path}[${index}]`);\n if (problem) return problem;\n }\n }\n }\n if (typeOf(value) === 'object') {\n const object = value as Record<string, unknown>;\n const { properties, required, additionalProperties } = schema as {\n properties?: Record<string, JsonSchema>;\n required?: string[];\n additionalProperties?: boolean;\n };\n for (const key of required ?? []) {\n if (object[key] === undefined) {\n return {\n path: `${path}.${key}`,\n message: `${path}.${key} is required`,\n };\n }\n }\n for (const [key, entry] of Object.entries(object)) {\n const child = properties?.[key];\n if (!child) {\n if (additionalProperties === false) {\n return {\n path: `${path}.${key}`,\n message: `${path}.${key} is not an option of this command`,\n };\n }\n continue;\n }\n if (entry === undefined) continue;\n const problem = checkSchema(child, entry, `${path}.${key}`);\n if (problem) return problem;\n }\n }\n return null;\n}\n\nexport type CommandSchemas = Readonly<Record<CommandName, JsonSchema>>;\n\n/** The input schemas by command name. */\nexport function commandSchemas(catalog: EngineCatalog): CommandSchemas {\n return Object.fromEntries(\n buildCommandTools(catalog).map((tool) => [tool.name, tool.inputSchema]),\n ) as CommandSchemas;\n}\n"],"mappings":"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"}