@elizaos/plugin-form 2.0.3-beta.6 → 2.0.3-beta.7
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/dist/actions/form.d.ts +31 -0
- package/dist/actions/form.d.ts.map +1 -0
- package/dist/actions/form.js +187 -0
- package/dist/actions/form.js.map +1 -0
- package/dist/builder.d.ts +320 -0
- package/dist/builder.d.ts.map +1 -0
- package/dist/builder.js +458 -0
- package/dist/builder.js.map +1 -0
- package/dist/builtins.d.ts +128 -0
- package/dist/builtins.d.ts.map +1 -0
- package/dist/builtins.js +233 -0
- package/dist/builtins.js.map +1 -0
- package/dist/defaults.d.ts +95 -0
- package/dist/defaults.d.ts.map +1 -0
- package/dist/defaults.js +79 -0
- package/dist/defaults.js.map +1 -0
- package/dist/evaluators/extractor.d.ts +28 -0
- package/dist/evaluators/extractor.d.ts.map +1 -0
- package/dist/evaluators/extractor.js +251 -0
- package/dist/evaluators/extractor.js.map +1 -0
- package/dist/extraction.d.ts +55 -0
- package/dist/extraction.d.ts.map +1 -0
- package/dist/extraction.js +347 -0
- package/dist/extraction.js.map +1 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +149 -0
- package/dist/index.js.map +1 -0
- package/dist/providers/context.d.ts +56 -0
- package/dist/providers/context.d.ts.map +1 -0
- package/dist/providers/context.js +204 -0
- package/dist/providers/context.js.map +1 -0
- package/dist/service.d.ts +402 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/service.js +1199 -0
- package/dist/service.js.map +1 -0
- package/dist/storage.d.ts +228 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +255 -0
- package/dist/storage.js.map +1 -0
- package/dist/template.d.ts +10 -0
- package/dist/template.d.ts.map +1 -0
- package/dist/template.js +60 -0
- package/dist/template.js.map +1 -0
- package/dist/ttl.d.ts +144 -0
- package/dist/ttl.d.ts.map +1 -0
- package/dist/ttl.js +85 -0
- package/dist/ttl.js.map +1 -0
- package/dist/types.d.ts +1213 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +39 -0
- package/dist/types.js.map +1 -0
- package/dist/validation.d.ts +156 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +289 -0
- package/dist/validation.js.map +1 -0
- package/package.json +3 -3
package/dist/builtins.js
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
2
|
+
const ISO_DATE_REGEX = /^\d{4}-\d{2}-\d{2}$/;
|
|
3
|
+
const textType = {
|
|
4
|
+
id: "text",
|
|
5
|
+
builtin: true,
|
|
6
|
+
validate: (value, control) => {
|
|
7
|
+
if (value === null || value === void 0) {
|
|
8
|
+
return { valid: true };
|
|
9
|
+
}
|
|
10
|
+
const str = String(value);
|
|
11
|
+
if (control.minLength !== void 0 && str.length < control.minLength) {
|
|
12
|
+
return {
|
|
13
|
+
valid: false,
|
|
14
|
+
error: `Must be at least ${control.minLength} characters`
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
if (control.maxLength !== void 0 && str.length > control.maxLength) {
|
|
18
|
+
return {
|
|
19
|
+
valid: false,
|
|
20
|
+
error: `Must be at most ${control.maxLength} characters`
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
if (control.pattern) {
|
|
24
|
+
const regex = new RegExp(control.pattern);
|
|
25
|
+
if (!regex.test(str)) {
|
|
26
|
+
return { valid: false, error: "Invalid format" };
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (control.enum && !control.enum.includes(str)) {
|
|
30
|
+
return {
|
|
31
|
+
valid: false,
|
|
32
|
+
error: `Must be one of: ${control.enum.join(", ")}`
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
return { valid: true };
|
|
36
|
+
},
|
|
37
|
+
parse: (value) => String(value).trim(),
|
|
38
|
+
format: (value) => String(value ?? ""),
|
|
39
|
+
extractionPrompt: "a text string"
|
|
40
|
+
};
|
|
41
|
+
const numberType = {
|
|
42
|
+
id: "number",
|
|
43
|
+
builtin: true,
|
|
44
|
+
validate: (value, control) => {
|
|
45
|
+
if (value === null || value === void 0 || value === "") {
|
|
46
|
+
return { valid: true };
|
|
47
|
+
}
|
|
48
|
+
const num = typeof value === "number" ? value : parseFloat(String(value));
|
|
49
|
+
if (!Number.isFinite(num)) {
|
|
50
|
+
return { valid: false, error: "Must be a valid number" };
|
|
51
|
+
}
|
|
52
|
+
if (control.min !== void 0 && num < control.min) {
|
|
53
|
+
return { valid: false, error: `Must be at least ${control.min}` };
|
|
54
|
+
}
|
|
55
|
+
if (control.max !== void 0 && num > control.max) {
|
|
56
|
+
return { valid: false, error: `Must be at most ${control.max}` };
|
|
57
|
+
}
|
|
58
|
+
return { valid: true };
|
|
59
|
+
},
|
|
60
|
+
parse: (value) => {
|
|
61
|
+
const cleaned = value.replace(/[,$\s]/g, "");
|
|
62
|
+
return parseFloat(cleaned);
|
|
63
|
+
},
|
|
64
|
+
format: (value) => {
|
|
65
|
+
if (value === null || value === void 0) return "";
|
|
66
|
+
const num = typeof value === "number" ? value : parseFloat(String(value));
|
|
67
|
+
if (Number.isNaN(num)) return String(value);
|
|
68
|
+
return num.toLocaleString();
|
|
69
|
+
},
|
|
70
|
+
extractionPrompt: "a number (integer or decimal)"
|
|
71
|
+
};
|
|
72
|
+
const emailType = {
|
|
73
|
+
id: "email",
|
|
74
|
+
builtin: true,
|
|
75
|
+
validate: (value) => {
|
|
76
|
+
if (value === null || value === void 0 || value === "") {
|
|
77
|
+
return { valid: true };
|
|
78
|
+
}
|
|
79
|
+
const str = String(value).trim().toLowerCase();
|
|
80
|
+
if (!EMAIL_REGEX.test(str)) {
|
|
81
|
+
return { valid: false, error: "Invalid email format" };
|
|
82
|
+
}
|
|
83
|
+
return { valid: true };
|
|
84
|
+
},
|
|
85
|
+
parse: (value) => value.trim().toLowerCase(),
|
|
86
|
+
format: (value) => String(value ?? "").toLowerCase(),
|
|
87
|
+
extractionPrompt: "an email address (e.g., user@example.com)"
|
|
88
|
+
};
|
|
89
|
+
const booleanType = {
|
|
90
|
+
id: "boolean",
|
|
91
|
+
builtin: true,
|
|
92
|
+
validate: (value) => {
|
|
93
|
+
if (value === null || value === void 0) {
|
|
94
|
+
return { valid: true };
|
|
95
|
+
}
|
|
96
|
+
if (typeof value === "boolean") {
|
|
97
|
+
return { valid: true };
|
|
98
|
+
}
|
|
99
|
+
const str = String(value).toLowerCase();
|
|
100
|
+
const validValues = ["true", "false", "yes", "no", "1", "0", "on", "off"];
|
|
101
|
+
if (!validValues.includes(str)) {
|
|
102
|
+
return { valid: false, error: "Must be yes/no or true/false" };
|
|
103
|
+
}
|
|
104
|
+
return { valid: true };
|
|
105
|
+
},
|
|
106
|
+
parse: (value) => {
|
|
107
|
+
const str = value.toLowerCase();
|
|
108
|
+
return ["true", "yes", "1", "on"].includes(str);
|
|
109
|
+
},
|
|
110
|
+
format: (value) => {
|
|
111
|
+
if (value === true) return "Yes";
|
|
112
|
+
if (value === false) return "No";
|
|
113
|
+
return String(value ?? "");
|
|
114
|
+
},
|
|
115
|
+
extractionPrompt: "a yes/no or true/false value"
|
|
116
|
+
};
|
|
117
|
+
const selectType = {
|
|
118
|
+
id: "select",
|
|
119
|
+
builtin: true,
|
|
120
|
+
validate: (value, control) => {
|
|
121
|
+
if (value === null || value === void 0 || value === "") {
|
|
122
|
+
return { valid: true };
|
|
123
|
+
}
|
|
124
|
+
const str = String(value);
|
|
125
|
+
if (control.options) {
|
|
126
|
+
const validValues = control.options.map((o) => o.value);
|
|
127
|
+
if (!validValues.includes(str)) {
|
|
128
|
+
const labels = control.options.map((o) => o.label).join(", ");
|
|
129
|
+
return { valid: false, error: `Must be one of: ${labels}` };
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (control.enum && !control.options) {
|
|
133
|
+
if (!control.enum.includes(str)) {
|
|
134
|
+
return {
|
|
135
|
+
valid: false,
|
|
136
|
+
error: `Must be one of: ${control.enum.join(", ")}`
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return { valid: true };
|
|
141
|
+
},
|
|
142
|
+
parse: (value) => value.trim(),
|
|
143
|
+
format: (value) => String(value ?? ""),
|
|
144
|
+
// Note: extractionPrompt is typically customized per-field with option labels
|
|
145
|
+
extractionPrompt: "one of the available options"
|
|
146
|
+
};
|
|
147
|
+
const dateType = {
|
|
148
|
+
id: "date",
|
|
149
|
+
builtin: true,
|
|
150
|
+
validate: (value) => {
|
|
151
|
+
if (value === null || value === void 0 || value === "") {
|
|
152
|
+
return { valid: true };
|
|
153
|
+
}
|
|
154
|
+
const str = String(value);
|
|
155
|
+
if (!ISO_DATE_REGEX.test(str)) {
|
|
156
|
+
return { valid: false, error: "Must be in YYYY-MM-DD format" };
|
|
157
|
+
}
|
|
158
|
+
const date = new Date(str);
|
|
159
|
+
if (Number.isNaN(date.getTime())) {
|
|
160
|
+
return { valid: false, error: "Invalid date" };
|
|
161
|
+
}
|
|
162
|
+
return { valid: true };
|
|
163
|
+
},
|
|
164
|
+
parse: (value) => {
|
|
165
|
+
const date = new Date(value);
|
|
166
|
+
if (!Number.isNaN(date.getTime())) {
|
|
167
|
+
return date.toISOString().split("T")[0];
|
|
168
|
+
}
|
|
169
|
+
return value.trim();
|
|
170
|
+
},
|
|
171
|
+
format: (value) => {
|
|
172
|
+
if (!value) return "";
|
|
173
|
+
const date = new Date(String(value));
|
|
174
|
+
if (Number.isNaN(date.getTime())) return String(value);
|
|
175
|
+
return date.toLocaleDateString();
|
|
176
|
+
},
|
|
177
|
+
extractionPrompt: "a date (preferably in YYYY-MM-DD format)"
|
|
178
|
+
};
|
|
179
|
+
const fileType = {
|
|
180
|
+
id: "file",
|
|
181
|
+
builtin: true,
|
|
182
|
+
validate: (value, _control) => {
|
|
183
|
+
if (value === null || value === void 0) {
|
|
184
|
+
return { valid: true };
|
|
185
|
+
}
|
|
186
|
+
if (typeof value === "object") {
|
|
187
|
+
return { valid: true };
|
|
188
|
+
}
|
|
189
|
+
return { valid: false, error: "Invalid file data" };
|
|
190
|
+
},
|
|
191
|
+
format: (value) => {
|
|
192
|
+
if (!value) return "";
|
|
193
|
+
if (Array.isArray(value)) {
|
|
194
|
+
return `${value.length} file(s)`;
|
|
195
|
+
}
|
|
196
|
+
if (typeof value === "object" && value !== null && "name" in value) {
|
|
197
|
+
return String(value.name);
|
|
198
|
+
}
|
|
199
|
+
return "File attached";
|
|
200
|
+
},
|
|
201
|
+
extractionPrompt: "a file attachment (upload required)"
|
|
202
|
+
};
|
|
203
|
+
const BUILTIN_TYPES = [
|
|
204
|
+
textType,
|
|
205
|
+
numberType,
|
|
206
|
+
emailType,
|
|
207
|
+
booleanType,
|
|
208
|
+
selectType,
|
|
209
|
+
dateType,
|
|
210
|
+
fileType
|
|
211
|
+
];
|
|
212
|
+
const BUILTIN_TYPE_MAP = new Map(
|
|
213
|
+
BUILTIN_TYPES.map((t) => [t.id, t])
|
|
214
|
+
);
|
|
215
|
+
function registerBuiltinTypes(registerFn) {
|
|
216
|
+
for (const type of BUILTIN_TYPES) {
|
|
217
|
+
registerFn(type);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
function getBuiltinType(id) {
|
|
221
|
+
return BUILTIN_TYPE_MAP.get(id);
|
|
222
|
+
}
|
|
223
|
+
function isBuiltinType(id) {
|
|
224
|
+
return BUILTIN_TYPE_MAP.has(id);
|
|
225
|
+
}
|
|
226
|
+
export {
|
|
227
|
+
BUILTIN_TYPES,
|
|
228
|
+
BUILTIN_TYPE_MAP,
|
|
229
|
+
getBuiltinType,
|
|
230
|
+
isBuiltinType,
|
|
231
|
+
registerBuiltinTypes
|
|
232
|
+
};
|
|
233
|
+
//# sourceMappingURL=builtins.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/builtins.ts"],"sourcesContent":["/**\n * @module builtins\n * @description Built-in control types for the Form Plugin\n *\n * ## Overview\n *\n * This module defines the standard control types that are available out of the box:\n * - text: Plain text strings\n * - number: Numeric values (integers or decimals)\n * - email: Email addresses with format validation\n * - boolean: Yes/no, true/false values\n * - select: Choice from predefined options\n * - date: Date values in various formats\n * - file: File uploads (handled specially)\n *\n * ## Why Built-in Types\n *\n * Built-in types provide:\n * 1. **Consistent validation** across all forms - same rules everywhere\n * 2. **Sensible defaults** for common field types - less configuration\n * 3. **LLM extraction hints** optimized for each type - better extraction\n * 4. **Override protection** to prevent accidental shadowing - safety first\n *\n * ## Architecture Decision: ControlType vs TypeHandler\n *\n * We use ControlType (not the legacy TypeHandler) because:\n * - ControlType is the shared interface for all type categories\n * - ControlType supports composite types (subcontrols)\n * - ControlType supports external types (activate/confirm)\n * - TypeHandler is legacy and maintained only for backwards compatibility\n *\n * ## Why These Specific Types\n *\n * | Type | Why Built-in |\n * |------|--------------|\n * | text | Most common field type, catch-all for strings |\n * | number | Second most common, needs special parsing (commas, $) |\n * | email | Critical for communication, has clear format rules |\n * | boolean | Binary choice, many natural language forms (yes/no/true/false) |\n * | select | Constrained choice, validation against options |\n * | date | Complex parsing (many formats), needs normalization |\n * | file | Special handling needed (size, type, storage) |\n *\n * ## Extending\n *\n * Plugins can register custom types via FormService.registerControlType().\n * Built-in types can be overridden with { allowOverride: true } option,\n * but this will log a warning.\n *\n * ## Usage\n *\n * Built-in types are automatically registered when FormService starts.\n * You don't need to call registerBuiltinTypes() manually.\n *\n * @example\n * ```typescript\n * // Check if a type is built-in before overriding\n * if (isBuiltinType('email')) {\n * console.log('Warning: overriding built-in type');\n * formService.registerControlType(myEmailType, { allowOverride: true });\n * }\n * ```\n */\n\nimport type { JsonValue } from \"@elizaos/core\";\nimport type { ControlType, FormControl, ValidationResult } from \"./types.js\";\n\n// ============================================================================\n// VALIDATION HELPERS\n// ============================================================================\n\n/**\n * Email regex pattern\n * WHY this pattern:\n * - Balances strictness with practicality\n * - Catches obvious errors (missing @, missing domain)\n * - Allows international characters in local part\n */\nconst EMAIL_REGEX = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n\n/**\n * ISO date regex pattern\n * WHY this pattern:\n * - Matches YYYY-MM-DD format\n * - Common standard for data exchange\n * - LLM can normalize other formats to this\n */\nconst ISO_DATE_REGEX = /^\\d{4}-\\d{2}-\\d{2}$/;\n\n// ============================================================================\n// BUILT-IN CONTROL TYPES\n// ============================================================================\n\n/**\n * Text control type\n *\n * The default type for string input. Accepts any string value.\n * Validation is typically done via pattern/minLength/maxLength on the control.\n *\n * WHY text is the default:\n * - Most form fields are ultimately strings\n * - Pattern matching handles most custom validation needs\n * - Length limits catch data quality issues\n * - Unknown types fall back to text safely\n *\n * WHY validate inside the ControlType:\n * - Centralizes string validation logic\n * - Control-level rules (minLength, maxLength, pattern) are checked here\n * - Allows consistent error messages\n */\nconst textType: ControlType = {\n id: \"text\",\n builtin: true,\n\n validate: (value: JsonValue, control: FormControl): ValidationResult => {\n if (value === null || value === undefined) {\n return { valid: true }; // Empty is valid; required check is separate\n }\n\n const str = String(value);\n\n // Check minLength if specified\n if (control.minLength !== undefined && str.length < control.minLength) {\n return {\n valid: false,\n error: `Must be at least ${control.minLength} characters`,\n };\n }\n\n // Check maxLength if specified\n if (control.maxLength !== undefined && str.length > control.maxLength) {\n return {\n valid: false,\n error: `Must be at most ${control.maxLength} characters`,\n };\n }\n\n // Check pattern if specified\n if (control.pattern) {\n const regex = new RegExp(control.pattern);\n if (!regex.test(str)) {\n return { valid: false, error: \"Invalid format\" };\n }\n }\n\n // Check enum if specified\n if (control.enum && !control.enum.includes(str)) {\n return {\n valid: false,\n error: `Must be one of: ${control.enum.join(\", \")}`,\n };\n }\n\n return { valid: true };\n },\n\n parse: (value: string): string => String(value).trim(),\n\n format: (value: JsonValue): string => String(value ?? \"\"),\n\n extractionPrompt: \"a text string\",\n};\n\n/**\n * Number control type\n *\n * Handles numeric values including integers and decimals.\n * Supports min/max validation for value range.\n *\n * WHY special number handling:\n * - Users type \"1,234\" or \"$50\" - we need to parse through formatting\n * - LLM might extract \"fifty\" which needs conversion\n * - Min/max validation is numeric, not string length\n *\n * WHY parse removes commas and currency symbols:\n * - International number formats use , as thousands separator\n * - Users often include currency prefix ($, €)\n * - Agent should handle natural input, not require clean numbers\n */\nconst numberType: ControlType = {\n id: \"number\",\n builtin: true,\n\n validate: (value: JsonValue, control: FormControl): ValidationResult => {\n if (value === null || value === undefined || value === \"\") {\n return { valid: true }; // Empty is valid; required check is separate\n }\n\n const num = typeof value === \"number\" ? value : parseFloat(String(value));\n\n if (!Number.isFinite(num)) {\n return { valid: false, error: \"Must be a valid number\" };\n }\n\n // Check min if specified\n if (control.min !== undefined && num < control.min) {\n return { valid: false, error: `Must be at least ${control.min}` };\n }\n\n // Check max if specified\n if (control.max !== undefined && num > control.max) {\n return { valid: false, error: `Must be at most ${control.max}` };\n }\n\n return { valid: true };\n },\n\n parse: (value: string): number => {\n const cleaned = value.replace(/[,$\\s]/g, \"\"); // Remove common formatting\n return parseFloat(cleaned);\n },\n\n format: (value: JsonValue): string => {\n if (value === null || value === undefined) return \"\";\n const num = typeof value === \"number\" ? value : parseFloat(String(value));\n if (Number.isNaN(num)) return String(value);\n return num.toLocaleString();\n },\n\n extractionPrompt: \"a number (integer or decimal)\",\n};\n\n/**\n * Email control type\n *\n * Validates email format using a practical regex.\n * Not RFC-compliant but catches common errors.\n *\n * WHY simple regex (not RFC 5322):\n * - RFC-compliant regex is 1000+ chars and still misses edge cases\n * - Simple pattern catches 99% of typos (missing @, missing domain)\n * - False positives are rare and harmless\n * - Real validation is done by sending confirmation email\n *\n * WHY lowercase normalization:\n * - Email local parts are technically case-sensitive (but rarely in practice)\n * - Domain is case-insensitive by standard\n * - Consistent lowercase prevents duplicate account issues\n */\nconst emailType: ControlType = {\n id: \"email\",\n builtin: true,\n\n validate: (value: JsonValue): ValidationResult => {\n if (value === null || value === undefined || value === \"\") {\n return { valid: true }; // Empty is valid; required check is separate\n }\n\n const str = String(value).trim().toLowerCase();\n\n if (!EMAIL_REGEX.test(str)) {\n return { valid: false, error: \"Invalid email format\" };\n }\n\n return { valid: true };\n },\n\n parse: (value: string): string => value.trim().toLowerCase(),\n\n format: (value: JsonValue): string => String(value ?? \"\").toLowerCase(),\n\n extractionPrompt: \"an email address (e.g., user@example.com)\",\n};\n\n/**\n * Boolean control type\n *\n * Handles yes/no, true/false, on/off values.\n * LLM can normalize natural language to boolean.\n *\n * WHY accept many formats:\n * - Users say \"yes\", \"yep\", \"sure\", \"absolutely\"\n * - Different cultures prefer different affirmatives\n * - LLM normalizes to one of the accepted formats\n * - We just need to recognize the normalized output\n *\n * WHY display as \"Yes\"/\"No\":\n * - More human-readable than true/false\n * - Consistent with how agent should speak\n */\nconst booleanType: ControlType = {\n id: \"boolean\",\n builtin: true,\n\n validate: (value: JsonValue): ValidationResult => {\n if (value === null || value === undefined) {\n return { valid: true }; // Empty is valid; required check is separate\n }\n\n if (typeof value === \"boolean\") {\n return { valid: true };\n }\n\n const str = String(value).toLowerCase();\n const validValues = [\"true\", \"false\", \"yes\", \"no\", \"1\", \"0\", \"on\", \"off\"];\n\n if (!validValues.includes(str)) {\n return { valid: false, error: \"Must be yes/no or true/false\" };\n }\n\n return { valid: true };\n },\n\n parse: (value: string): boolean => {\n const str = value.toLowerCase();\n return [\"true\", \"yes\", \"1\", \"on\"].includes(str);\n },\n\n format: (value: JsonValue): string => {\n if (value === true) return \"Yes\";\n if (value === false) return \"No\";\n return String(value ?? \"\");\n },\n\n extractionPrompt: \"a yes/no or true/false value\",\n};\n\n/**\n * Select control type\n *\n * Choice from predefined options. Options come from the control definition.\n * Validates that value matches one of the allowed options.\n *\n * WHY strict option matching:\n * - Select fields have defined, constrained values\n * - Invalid selections indicate extraction error, not user error\n * - Better to reject and re-ask than accept garbage\n *\n * WHY show option labels in error:\n * - User might not know the exact value needed\n * - Showing valid options helps them choose\n * - Agent can use this for more helpful prompts\n */\nconst selectType: ControlType = {\n id: \"select\",\n builtin: true,\n\n validate: (value: JsonValue, control: FormControl): ValidationResult => {\n if (value === null || value === undefined || value === \"\") {\n return { valid: true }; // Empty is valid; required check is separate\n }\n\n const str = String(value);\n\n // Check against options if defined\n if (control.options) {\n const validValues = control.options.map((o) => o.value);\n if (!validValues.includes(str)) {\n const labels = control.options.map((o) => o.label).join(\", \");\n return { valid: false, error: `Must be one of: ${labels}` };\n }\n }\n\n // Check against enum if defined (fallback)\n if (control.enum && !control.options) {\n if (!control.enum.includes(str)) {\n return {\n valid: false,\n error: `Must be one of: ${control.enum.join(\", \")}`,\n };\n }\n }\n\n return { valid: true };\n },\n\n parse: (value: string): string => value.trim(),\n\n format: (value: JsonValue): string => String(value ?? \"\"),\n\n // Note: extractionPrompt is typically customized per-field with option labels\n extractionPrompt: \"one of the available options\",\n};\n\n/**\n * Date control type\n *\n * Handles date values in ISO format (YYYY-MM-DD).\n * LLM normalizes various date formats to ISO.\n *\n * WHY ISO format as canonical:\n * - Unambiguous (no 01/02 confusion between US/EU)\n * - Sortable as strings\n * - Standard for data exchange\n *\n * WHY flexible parsing:\n * - Users say \"tomorrow\", \"next Monday\", \"12/25/2024\"\n * - LLM should normalize to ISO\n * - We accept anything Date() can parse as fallback\n *\n * WHY locale display:\n * - Agent should speak in user's date format\n * - toLocaleDateString() adapts automatically\n */\nconst dateType: ControlType = {\n id: \"date\",\n builtin: true,\n\n validate: (value: JsonValue): ValidationResult => {\n if (value === null || value === undefined || value === \"\") {\n return { valid: true }; // Empty is valid; required check is separate\n }\n\n const str = String(value);\n\n // Check ISO format\n if (!ISO_DATE_REGEX.test(str)) {\n return { valid: false, error: \"Must be in YYYY-MM-DD format\" };\n }\n\n // Check if it's a valid date\n const date = new Date(str);\n if (Number.isNaN(date.getTime())) {\n return { valid: false, error: \"Invalid date\" };\n }\n\n return { valid: true };\n },\n\n parse: (value: string): string => {\n // Try to parse and normalize to ISO\n const date = new Date(value);\n if (!Number.isNaN(date.getTime())) {\n return date.toISOString().split(\"T\")[0];\n }\n return value.trim();\n },\n\n format: (value: JsonValue): string => {\n if (!value) return \"\";\n const date = new Date(String(value));\n if (Number.isNaN(date.getTime())) return String(value);\n return date.toLocaleDateString();\n },\n\n extractionPrompt: \"a date (preferably in YYYY-MM-DD format)\",\n};\n\n/**\n * File control type\n *\n * Metadata-only control for file uploads. Actual file handling is done by the\n * file upload pipeline, not the form system.\n *\n * Validation here is basic; real validation happens during upload.\n *\n * WHY separate from other types:\n * - File content isn't serializable in session state\n * - Actual upload is handled by messaging platform\n * - We only store metadata (name, size, mimeType, url)\n *\n * WHY validation is minimal:\n * - Real file validation happens at upload time\n * - By the time we see it, it's already uploaded\n * - We just validate the metadata structure\n *\n * WHY display as \"N file(s)\":\n * - File content isn't meaningful to show\n * - Count and names are what user cares about\n */\nconst fileType: ControlType = {\n id: \"file\",\n builtin: true,\n\n validate: (value: JsonValue, _control: FormControl): ValidationResult => {\n if (value === null || value === undefined) {\n return { valid: true }; // Empty is valid; required check is separate\n }\n\n // For file type, value is typically a FieldFile or array of FieldFile\n // Basic validation - detailed validation happens in upload pipeline\n if (typeof value === \"object\") {\n return { valid: true };\n }\n\n return { valid: false, error: \"Invalid file data\" };\n },\n\n format: (value: JsonValue): string => {\n if (!value) return \"\";\n if (Array.isArray(value)) {\n return `${value.length} file(s)`;\n }\n if (typeof value === \"object\" && value !== null && \"name\" in value) {\n return String((value as { name: string }).name);\n }\n return \"File attached\";\n },\n\n extractionPrompt: \"a file attachment (upload required)\",\n};\n\n// ============================================================================\n// EXPORTS\n// ============================================================================\n\n/**\n * Array of all built-in control types.\n *\n * These are registered automatically when FormService starts.\n * The order doesn't matter; they're looked up by id.\n *\n * WHY array + map:\n * - Array allows easy iteration for registration\n * - Map provides O(1) lookup for override checks\n * - Both derived from same source ensures consistency\n */\nexport const BUILTIN_TYPES: ControlType[] = [\n textType,\n numberType,\n emailType,\n booleanType,\n selectType,\n dateType,\n fileType,\n];\n\n/**\n * Map of built-in types by id for quick lookup.\n *\n * WHY Map (not object):\n * - Type-safe keys\n * - O(1) lookup performance\n * - Clear has/get semantics\n */\nexport const BUILTIN_TYPE_MAP: Map<string, ControlType> = new Map(\n BUILTIN_TYPES.map((t) => [t.id, t]),\n);\n\n/**\n * Register all built-in types with a FormService instance.\n *\n * This is called automatically during FormService.start().\n * You typically don't need to call this directly.\n *\n * WHY take a function (not FormService):\n * - Avoids circular dependency (builtins.ts ↔ service.ts)\n * - Service passes its registerControlType method\n * - Clean separation of concerns\n *\n * @param registerFn - The FormService.registerControlType method\n */\nexport function registerBuiltinTypes(\n registerFn: (\n type: ControlType,\n options?: { allowOverride?: boolean },\n ) => void,\n): void {\n for (const type of BUILTIN_TYPES) {\n registerFn(type);\n }\n}\n\n/**\n * Get a built-in type by id.\n *\n * This is a convenience for checking if a type is built-in\n * before attempting to override it.\n *\n * WHY this exists:\n * - Plugins may want to extend a built-in type\n * - Checking before override allows informed decisions\n * - Avoids accidental shadowing\n *\n * @param id - The type id to look up\n * @returns The ControlType if found, undefined otherwise\n */\nexport function getBuiltinType(id: string): ControlType | undefined {\n return BUILTIN_TYPE_MAP.get(id);\n}\n\n/**\n * Check if a type id is a built-in type.\n *\n * WHY boolean convenience:\n * - Most callers just need yes/no answer\n * - Cleaner than checking getBuiltinType() !== undefined\n *\n * @param id - The type id to check\n * @returns true if the type is built-in\n */\nexport function isBuiltinType(id: string): boolean {\n return BUILTIN_TYPE_MAP.has(id);\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module defaults
|
|
3
|
+
* @description Default value application for forms and controls
|
|
4
|
+
*
|
|
5
|
+
* ## Purpose
|
|
6
|
+
*
|
|
7
|
+
* Form definitions can be quite minimal:
|
|
8
|
+
*
|
|
9
|
+
* ```typescript
|
|
10
|
+
* { id: 'contact', controls: [{ key: 'email' }] }
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* This module fills in sensible defaults:
|
|
14
|
+
*
|
|
15
|
+
* ```typescript
|
|
16
|
+
* {
|
|
17
|
+
* id: 'contact',
|
|
18
|
+
* name: 'Contact', // Prettified from id
|
|
19
|
+
* version: 1, // Default version
|
|
20
|
+
* status: 'active', // Default status
|
|
21
|
+
* controls: [{
|
|
22
|
+
* key: 'email',
|
|
23
|
+
* label: 'Email', // Prettified from key
|
|
24
|
+
* type: 'text', // Default type
|
|
25
|
+
* required: false, // Default required
|
|
26
|
+
* confirmThreshold: 0.8, // Default confidence threshold
|
|
27
|
+
* }],
|
|
28
|
+
* ux: { ... }, // All UX defaults
|
|
29
|
+
* ttl: { ... }, // All TTL defaults
|
|
30
|
+
* nudge: { ... }, // All nudge defaults
|
|
31
|
+
* debug: false, // Default debug off
|
|
32
|
+
* }
|
|
33
|
+
* ```
|
|
34
|
+
*
|
|
35
|
+
* ## When Defaults Apply
|
|
36
|
+
*
|
|
37
|
+
* Defaults are applied:
|
|
38
|
+
*
|
|
39
|
+
* 1. **At Registration**: When a form is registered via FormService
|
|
40
|
+
* 2. **At Build**: When using FormBuilder.build()
|
|
41
|
+
*
|
|
42
|
+
* This ensures:
|
|
43
|
+
* - Minimal definitions work correctly
|
|
44
|
+
* - All optional fields have values
|
|
45
|
+
* - Code can rely on values being present
|
|
46
|
+
*
|
|
47
|
+
* ## Default Values Philosophy
|
|
48
|
+
*
|
|
49
|
+
* Default values were chosen to be:
|
|
50
|
+
*
|
|
51
|
+
* - **Safe**: Won't cause unexpected behavior
|
|
52
|
+
* - **User-Friendly**: Enable features users expect
|
|
53
|
+
* - **Minimal**: Don't add unnecessary restrictions
|
|
54
|
+
*
|
|
55
|
+
* For example:
|
|
56
|
+
* - TTL defaults are generous (14-90 days)
|
|
57
|
+
* - UX features are enabled by default
|
|
58
|
+
* - Required defaults to false (explicit opt-in)
|
|
59
|
+
*/
|
|
60
|
+
import type { FormControl, FormDefinition } from "./types";
|
|
61
|
+
/**
|
|
62
|
+
* Apply defaults to a FormControl.
|
|
63
|
+
*
|
|
64
|
+
* Ensures all optional fields have values.
|
|
65
|
+
*
|
|
66
|
+
* @param control - Partial control to complete
|
|
67
|
+
* @returns Complete FormControl with all defaults applied
|
|
68
|
+
*/
|
|
69
|
+
export declare function applyControlDefaults(control: Partial<FormControl>): FormControl;
|
|
70
|
+
/**
|
|
71
|
+
* Apply defaults to a FormDefinition.
|
|
72
|
+
*
|
|
73
|
+
* Ensures all optional fields have values and applies
|
|
74
|
+
* defaults to all controls.
|
|
75
|
+
*
|
|
76
|
+
* @param form - Partial form to complete
|
|
77
|
+
* @returns Complete FormDefinition with all defaults applied
|
|
78
|
+
*/
|
|
79
|
+
export declare function applyFormDefaults(form: Partial<FormDefinition>): FormDefinition;
|
|
80
|
+
/**
|
|
81
|
+
* Convert snake_case or kebab-case to Title Case.
|
|
82
|
+
*
|
|
83
|
+
* Used to generate human-readable labels from field keys
|
|
84
|
+
* and form names from form IDs.
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* prettify('first_name') // "First Name"
|
|
88
|
+
* prettify('email-address') // "Email Address"
|
|
89
|
+
* prettify('userId') // "UserId" (camelCase preserved)
|
|
90
|
+
*
|
|
91
|
+
* @param key - The key to prettify
|
|
92
|
+
* @returns Human-readable title case string
|
|
93
|
+
*/
|
|
94
|
+
export declare function prettify(key: string): string;
|
|
95
|
+
//# sourceMappingURL=defaults.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../src/defaults.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0DG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAG3D;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,OAAO,CAAC,WAAW,CAAC,GAC5B,WAAW,CAwBb;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,OAAO,CAAC,cAAc,CAAC,GAC5B,cAAc,CA6DhB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAQ5C"}
|
package/dist/defaults.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { FORM_CONTROL_DEFAULTS, FORM_DEFINITION_DEFAULTS } from "./types.js";
|
|
2
|
+
function applyControlDefaults(control) {
|
|
3
|
+
const key = control.key;
|
|
4
|
+
if (!key) {
|
|
5
|
+
throw new Error("Control key is required");
|
|
6
|
+
}
|
|
7
|
+
return {
|
|
8
|
+
// Required field (must be present)
|
|
9
|
+
key,
|
|
10
|
+
// Derive label from key if not provided
|
|
11
|
+
// WHY: User sees labels, default should be readable
|
|
12
|
+
label: control.label || prettify(key),
|
|
13
|
+
// Default type is text (most common)
|
|
14
|
+
type: control.type || FORM_CONTROL_DEFAULTS.type,
|
|
15
|
+
// Default not required (explicit opt-in)
|
|
16
|
+
// WHY: Safer to require opt-in for required fields
|
|
17
|
+
required: control.required ?? FORM_CONTROL_DEFAULTS.required,
|
|
18
|
+
// Default confidence threshold for auto-acceptance
|
|
19
|
+
// WHY 0.8: High enough to be confident, low enough to be useful
|
|
20
|
+
confirmThreshold: control.confirmThreshold ?? FORM_CONTROL_DEFAULTS.confirmThreshold,
|
|
21
|
+
// Spread remaining properties (override defaults)
|
|
22
|
+
...control
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function applyFormDefaults(form) {
|
|
26
|
+
const id = form.id;
|
|
27
|
+
if (!id) {
|
|
28
|
+
throw new Error("Form id is required");
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
// Required fields
|
|
32
|
+
id,
|
|
33
|
+
// Derive name from id if not provided
|
|
34
|
+
name: form.name || prettify(id),
|
|
35
|
+
// Default version for schema tracking
|
|
36
|
+
version: form.version ?? FORM_DEFINITION_DEFAULTS.version,
|
|
37
|
+
// Default status is active
|
|
38
|
+
status: form.status ?? FORM_DEFINITION_DEFAULTS.status,
|
|
39
|
+
// Apply defaults to all controls
|
|
40
|
+
controls: (form.controls || []).map(applyControlDefaults),
|
|
41
|
+
// UX defaults - enable helpful features by default
|
|
42
|
+
// WHY enable by default: Better user experience out of the box
|
|
43
|
+
ux: {
|
|
44
|
+
allowUndo: form.ux?.allowUndo ?? FORM_DEFINITION_DEFAULTS.ux.allowUndo,
|
|
45
|
+
allowSkip: form.ux?.allowSkip ?? FORM_DEFINITION_DEFAULTS.ux.allowSkip,
|
|
46
|
+
maxUndoSteps: form.ux?.maxUndoSteps ?? FORM_DEFINITION_DEFAULTS.ux.maxUndoSteps,
|
|
47
|
+
showExamples: form.ux?.showExamples ?? FORM_DEFINITION_DEFAULTS.ux.showExamples,
|
|
48
|
+
showExplanations: form.ux?.showExplanations ?? FORM_DEFINITION_DEFAULTS.ux.showExplanations,
|
|
49
|
+
allowAutofill: form.ux?.allowAutofill ?? FORM_DEFINITION_DEFAULTS.ux.allowAutofill
|
|
50
|
+
},
|
|
51
|
+
// TTL defaults - generous retention
|
|
52
|
+
// WHY generous: Better to keep data too long than lose user work
|
|
53
|
+
ttl: {
|
|
54
|
+
minDays: form.ttl?.minDays ?? FORM_DEFINITION_DEFAULTS.ttl.minDays,
|
|
55
|
+
maxDays: form.ttl?.maxDays ?? FORM_DEFINITION_DEFAULTS.ttl.maxDays,
|
|
56
|
+
effortMultiplier: form.ttl?.effortMultiplier ?? FORM_DEFINITION_DEFAULTS.ttl.effortMultiplier
|
|
57
|
+
},
|
|
58
|
+
// Nudge defaults - helpful but not annoying
|
|
59
|
+
nudge: {
|
|
60
|
+
enabled: form.nudge?.enabled ?? FORM_DEFINITION_DEFAULTS.nudge.enabled,
|
|
61
|
+
afterInactiveHours: form.nudge?.afterInactiveHours ?? FORM_DEFINITION_DEFAULTS.nudge.afterInactiveHours,
|
|
62
|
+
maxNudges: form.nudge?.maxNudges ?? FORM_DEFINITION_DEFAULTS.nudge.maxNudges,
|
|
63
|
+
message: form.nudge?.message
|
|
64
|
+
},
|
|
65
|
+
// Debug defaults to off for performance
|
|
66
|
+
debug: form.debug ?? FORM_DEFINITION_DEFAULTS.debug,
|
|
67
|
+
// Spread remaining properties (override defaults)
|
|
68
|
+
...form
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function prettify(key) {
|
|
72
|
+
return key.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
73
|
+
}
|
|
74
|
+
export {
|
|
75
|
+
applyControlDefaults,
|
|
76
|
+
applyFormDefaults,
|
|
77
|
+
prettify
|
|
78
|
+
};
|
|
79
|
+
//# sourceMappingURL=defaults.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/defaults.ts"],"sourcesContent":["/**\n * @module defaults\n * @description Default value application for forms and controls\n *\n * ## Purpose\n *\n * Form definitions can be quite minimal:\n *\n * ```typescript\n * { id: 'contact', controls: [{ key: 'email' }] }\n * ```\n *\n * This module fills in sensible defaults:\n *\n * ```typescript\n * {\n * id: 'contact',\n * name: 'Contact', // Prettified from id\n * version: 1, // Default version\n * status: 'active', // Default status\n * controls: [{\n * key: 'email',\n * label: 'Email', // Prettified from key\n * type: 'text', // Default type\n * required: false, // Default required\n * confirmThreshold: 0.8, // Default confidence threshold\n * }],\n * ux: { ... }, // All UX defaults\n * ttl: { ... }, // All TTL defaults\n * nudge: { ... }, // All nudge defaults\n * debug: false, // Default debug off\n * }\n * ```\n *\n * ## When Defaults Apply\n *\n * Defaults are applied:\n *\n * 1. **At Registration**: When a form is registered via FormService\n * 2. **At Build**: When using FormBuilder.build()\n *\n * This ensures:\n * - Minimal definitions work correctly\n * - All optional fields have values\n * - Code can rely on values being present\n *\n * ## Default Values Philosophy\n *\n * Default values were chosen to be:\n *\n * - **Safe**: Won't cause unexpected behavior\n * - **User-Friendly**: Enable features users expect\n * - **Minimal**: Don't add unnecessary restrictions\n *\n * For example:\n * - TTL defaults are generous (14-90 days)\n * - UX features are enabled by default\n * - Required defaults to false (explicit opt-in)\n */\n\nimport type { FormControl, FormDefinition } from \"./types.js\";\nimport { FORM_CONTROL_DEFAULTS, FORM_DEFINITION_DEFAULTS } from \"./types.js\";\n\n/**\n * Apply defaults to a FormControl.\n *\n * Ensures all optional fields have values.\n *\n * @param control - Partial control to complete\n * @returns Complete FormControl with all defaults applied\n */\nexport function applyControlDefaults(\n control: Partial<FormControl>,\n): FormControl {\n const key = control.key;\n if (!key) {\n throw new Error(\"Control key is required\");\n }\n\n return {\n // Required field (must be present)\n key,\n // Derive label from key if not provided\n // WHY: User sees labels, default should be readable\n label: control.label || prettify(key),\n // Default type is text (most common)\n type: control.type || FORM_CONTROL_DEFAULTS.type,\n // Default not required (explicit opt-in)\n // WHY: Safer to require opt-in for required fields\n required: control.required ?? FORM_CONTROL_DEFAULTS.required,\n // Default confidence threshold for auto-acceptance\n // WHY 0.8: High enough to be confident, low enough to be useful\n confirmThreshold:\n control.confirmThreshold ?? FORM_CONTROL_DEFAULTS.confirmThreshold,\n // Spread remaining properties (override defaults)\n ...control,\n };\n}\n\n/**\n * Apply defaults to a FormDefinition.\n *\n * Ensures all optional fields have values and applies\n * defaults to all controls.\n *\n * @param form - Partial form to complete\n * @returns Complete FormDefinition with all defaults applied\n */\nexport function applyFormDefaults(\n form: Partial<FormDefinition>,\n): FormDefinition {\n const id = form.id;\n if (!id) {\n throw new Error(\"Form id is required\");\n }\n\n return {\n // Required fields\n id,\n // Derive name from id if not provided\n name: form.name || prettify(id),\n // Default version for schema tracking\n version: form.version ?? FORM_DEFINITION_DEFAULTS.version,\n // Default status is active\n status: form.status ?? FORM_DEFINITION_DEFAULTS.status,\n // Apply defaults to all controls\n controls: (form.controls || []).map(applyControlDefaults),\n\n // UX defaults - enable helpful features by default\n // WHY enable by default: Better user experience out of the box\n ux: {\n allowUndo: form.ux?.allowUndo ?? FORM_DEFINITION_DEFAULTS.ux.allowUndo,\n allowSkip: form.ux?.allowSkip ?? FORM_DEFINITION_DEFAULTS.ux.allowSkip,\n maxUndoSteps:\n form.ux?.maxUndoSteps ?? FORM_DEFINITION_DEFAULTS.ux.maxUndoSteps,\n showExamples:\n form.ux?.showExamples ?? FORM_DEFINITION_DEFAULTS.ux.showExamples,\n showExplanations:\n form.ux?.showExplanations ??\n FORM_DEFINITION_DEFAULTS.ux.showExplanations,\n allowAutofill:\n form.ux?.allowAutofill ?? FORM_DEFINITION_DEFAULTS.ux.allowAutofill,\n },\n\n // TTL defaults - generous retention\n // WHY generous: Better to keep data too long than lose user work\n ttl: {\n minDays: form.ttl?.minDays ?? FORM_DEFINITION_DEFAULTS.ttl.minDays,\n maxDays: form.ttl?.maxDays ?? FORM_DEFINITION_DEFAULTS.ttl.maxDays,\n effortMultiplier:\n form.ttl?.effortMultiplier ??\n FORM_DEFINITION_DEFAULTS.ttl.effortMultiplier,\n },\n\n // Nudge defaults - helpful but not annoying\n nudge: {\n enabled: form.nudge?.enabled ?? FORM_DEFINITION_DEFAULTS.nudge.enabled,\n afterInactiveHours:\n form.nudge?.afterInactiveHours ??\n FORM_DEFINITION_DEFAULTS.nudge.afterInactiveHours,\n maxNudges:\n form.nudge?.maxNudges ?? FORM_DEFINITION_DEFAULTS.nudge.maxNudges,\n message: form.nudge?.message,\n },\n\n // Debug defaults to off for performance\n debug: form.debug ?? FORM_DEFINITION_DEFAULTS.debug,\n\n // Spread remaining properties (override defaults)\n ...form,\n };\n}\n\n/**\n * Convert snake_case or kebab-case to Title Case.\n *\n * Used to generate human-readable labels from field keys\n * and form names from form IDs.\n *\n * @example\n * prettify('first_name') // \"First Name\"\n * prettify('email-address') // \"Email Address\"\n * prettify('userId') // \"UserId\" (camelCase preserved)\n *\n * @param key - The key to prettify\n * @returns Human-readable title case string\n */\nexport function prettify(key: string): string {\n return (\n key\n // Replace underscores and hyphens with spaces\n .replace(/[-_]/g, \" \")\n // Capitalize first letter of each word\n .replace(/\\b\\w/g, (c) => c.toUpperCase())\n );\n}\n"],"mappings":"AA6DA,SAAS,uBAAuB,gCAAgC;AAUzD,SAAS,qBACd,SACa;AACb,QAAM,MAAM,QAAQ;AACpB,MAAI,CAAC,KAAK;AACR,UAAM,IAAI,MAAM,yBAAyB;AAAA,EAC3C;AAEA,SAAO;AAAA;AAAA,IAEL;AAAA;AAAA;AAAA,IAGA,OAAO,QAAQ,SAAS,SAAS,GAAG;AAAA;AAAA,IAEpC,MAAM,QAAQ,QAAQ,sBAAsB;AAAA;AAAA;AAAA,IAG5C,UAAU,QAAQ,YAAY,sBAAsB;AAAA;AAAA;AAAA,IAGpD,kBACE,QAAQ,oBAAoB,sBAAsB;AAAA;AAAA,IAEpD,GAAG;AAAA,EACL;AACF;AAWO,SAAS,kBACd,MACgB;AAChB,QAAM,KAAK,KAAK;AAChB,MAAI,CAAC,IAAI;AACP,UAAM,IAAI,MAAM,qBAAqB;AAAA,EACvC;AAEA,SAAO;AAAA;AAAA,IAEL;AAAA;AAAA,IAEA,MAAM,KAAK,QAAQ,SAAS,EAAE;AAAA;AAAA,IAE9B,SAAS,KAAK,WAAW,yBAAyB;AAAA;AAAA,IAElD,QAAQ,KAAK,UAAU,yBAAyB;AAAA;AAAA,IAEhD,WAAW,KAAK,YAAY,CAAC,GAAG,IAAI,oBAAoB;AAAA;AAAA;AAAA,IAIxD,IAAI;AAAA,MACF,WAAW,KAAK,IAAI,aAAa,yBAAyB,GAAG;AAAA,MAC7D,WAAW,KAAK,IAAI,aAAa,yBAAyB,GAAG;AAAA,MAC7D,cACE,KAAK,IAAI,gBAAgB,yBAAyB,GAAG;AAAA,MACvD,cACE,KAAK,IAAI,gBAAgB,yBAAyB,GAAG;AAAA,MACvD,kBACE,KAAK,IAAI,oBACT,yBAAyB,GAAG;AAAA,MAC9B,eACE,KAAK,IAAI,iBAAiB,yBAAyB,GAAG;AAAA,IAC1D;AAAA;AAAA;AAAA,IAIA,KAAK;AAAA,MACH,SAAS,KAAK,KAAK,WAAW,yBAAyB,IAAI;AAAA,MAC3D,SAAS,KAAK,KAAK,WAAW,yBAAyB,IAAI;AAAA,MAC3D,kBACE,KAAK,KAAK,oBACV,yBAAyB,IAAI;AAAA,IACjC;AAAA;AAAA,IAGA,OAAO;AAAA,MACL,SAAS,KAAK,OAAO,WAAW,yBAAyB,MAAM;AAAA,MAC/D,oBACE,KAAK,OAAO,sBACZ,yBAAyB,MAAM;AAAA,MACjC,WACE,KAAK,OAAO,aAAa,yBAAyB,MAAM;AAAA,MAC1D,SAAS,KAAK,OAAO;AAAA,IACvB;AAAA;AAAA,IAGA,OAAO,KAAK,SAAS,yBAAyB;AAAA;AAAA,IAG9C,GAAG;AAAA,EACL;AACF;AAgBO,SAAS,SAAS,KAAqB;AAC5C,SACE,IAEG,QAAQ,SAAS,GAAG,EAEpB,QAAQ,SAAS,CAAC,MAAM,EAAE,YAAY,CAAC;AAE9C;","names":[]}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module evaluators/extractor
|
|
3
|
+
* @description Form post-turn evaluator that runs in the
|
|
4
|
+
* EvaluatorService pass. Detects form intents (submit, stash, cancel, undo,
|
|
5
|
+
* skip, autofill, info) and extracts field values from the user message,
|
|
6
|
+
* then mutates session state accordingly.
|
|
7
|
+
*
|
|
8
|
+
* The 'restore' intent is handled by FORM action=restore (a planner Action)
|
|
9
|
+
* so the restored form is in scope before the agent's response is generated.
|
|
10
|
+
*/
|
|
11
|
+
import type { Evaluator, UUID } from "@elizaos/core";
|
|
12
|
+
import type { FormService } from "../service";
|
|
13
|
+
import { type TemplateValues } from "../template";
|
|
14
|
+
import type { ExtractionResult, FormDefinition, FormIntent, FormSession } from "../types";
|
|
15
|
+
interface FormExtractorOutput {
|
|
16
|
+
formIntent: FormIntent;
|
|
17
|
+
formExtractions: ExtractionResult[];
|
|
18
|
+
}
|
|
19
|
+
interface FormExtractorPrepared {
|
|
20
|
+
formService: FormService;
|
|
21
|
+
session: FormSession;
|
|
22
|
+
form: FormDefinition;
|
|
23
|
+
templateValues: TemplateValues;
|
|
24
|
+
entityId: UUID;
|
|
25
|
+
}
|
|
26
|
+
export declare const formEvaluator: Evaluator<FormExtractorOutput, FormExtractorPrepared>;
|
|
27
|
+
export default formEvaluator;
|
|
28
|
+
//# sourceMappingURL=extractor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extractor.d.ts","sourceRoot":"","sources":["../../src/evaluators/extractor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EACV,SAAS,EAKT,IAAI,EACL,MAAM,eAAe,CAAC;AAQvB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,EAAuB,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AACvE,OAAO,KAAK,EACV,gBAAgB,EAChB,cAAc,EACd,UAAU,EACV,WAAW,EACZ,MAAM,UAAU,CAAC;AAElB,UAAU,mBAAmB;IAC3B,UAAU,EAAE,UAAU,CAAC;IACvB,eAAe,EAAE,gBAAgB,EAAE,CAAC;CACrC;AAED,UAAU,qBAAqB;IAC7B,WAAW,EAAE,WAAW,CAAC;IACzB,OAAO,EAAE,WAAW,CAAC;IACrB,IAAI,EAAE,cAAc,CAAC;IACrB,cAAc,EAAE,cAAc,CAAC;IAC/B,QAAQ,EAAE,IAAI,CAAC;CAChB;AA6ND,eAAO,MAAM,aAAa,EAAE,SAAS,CACnC,mBAAmB,EACnB,qBAAqB,CAkFtB,CAAC;AAEF,eAAe,aAAa,CAAC"}
|