@agentica/core 0.41.2 → 0.41.3

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.
@@ -6,7 +6,7 @@ import { Escaper } from "typia/lib/utils/Escaper";
6
6
 
7
7
  export const JsonUtil = {
8
8
  parse,
9
- stringifyValidateFailure,
9
+ stringifyValidationFailure,
10
10
  };
11
11
 
12
12
  const pipe = (...fns: ((str: string) => string)[]) => (str: string) => fns.reduce((acc, fn) => fn(acc), str);
@@ -16,7 +16,7 @@ function parse(str: string) {
16
16
  return JSON.parse(str);
17
17
  }
18
18
 
19
- function stringifyValidateFailure(
19
+ function stringifyValidationFailure(
20
20
  failure: IValidation.IFailure,
21
21
  ): string {
22
22
  const usedErrors = new Set<IValidation.IError>();
@@ -62,7 +62,28 @@ function stringify(props: {
62
62
 
63
63
  // Array
64
64
  if (Array.isArray(value)) {
65
+ // Check for missing array element errors (path[])
66
+ const missingElementErrors = getMissingArrayElementErrors(
67
+ path,
68
+ errors,
69
+ usedErrors,
70
+ );
71
+ const hasMissingElements = missingElementErrors.length > 0;
72
+
65
73
  if (value.length === 0) {
74
+ // Empty array but has missing element errors - show placeholders
75
+ if (hasMissingElements) {
76
+ const innerIndent = " ".repeat(tab + 1);
77
+ const lines: string[] = [];
78
+ lines.push(`${indent}[${errorComment}`);
79
+ missingElementErrors.forEach((e, idx) => {
80
+ const errComment = ` // ❌ ${JSON.stringify([{ path: e.path, expected: e.expected, description: e.description }])}`;
81
+ const comma = idx < missingElementErrors.length - 1 ? "," : "";
82
+ lines.push(`${innerIndent}undefined${comma}${errComment}`);
83
+ });
84
+ lines.push(`${indent}]`);
85
+ return lines.join("\n");
86
+ }
66
87
  return `${indent}[]${errorComment}`;
67
88
  }
68
89
 
@@ -71,6 +92,10 @@ function stringify(props: {
71
92
 
72
93
  value.forEach((item: unknown, index: number) => {
73
94
  const itemPath: string = `${path}[${index}]`;
95
+ const isLastElement = index === value.length - 1;
96
+ // If there are missing element errors, this is not truly the last line
97
+ const needsComma = !isLastElement || hasMissingElements;
98
+
74
99
  let itemStr: string = stringify({
75
100
  value: item,
76
101
  errors,
@@ -81,15 +106,15 @@ function stringify(props: {
81
106
  usedErrors,
82
107
  });
83
108
  // Add comma before the error comment if not the last element
84
- if (index < value.length - 1) {
109
+ if (needsComma) {
85
110
  const itemLines: string[] = itemStr.split("\n");
86
111
  const lastLine: string = itemLines[itemLines.length - 1]!;
87
112
  const commentIndex: number = lastLine.indexOf(" //");
88
113
  if (commentIndex !== -1) {
89
- itemLines[itemLines.length - 1]
90
- = `${lastLine.slice(0, commentIndex)
91
- },${
92
- lastLine.slice(commentIndex)}`;
114
+ itemLines[itemLines.length - 1] = `${lastLine.slice(
115
+ 0,
116
+ commentIndex,
117
+ )},${lastLine.slice(commentIndex)}`;
93
118
  }
94
119
  else {
95
120
  itemLines[itemLines.length - 1] += ",";
@@ -99,6 +124,16 @@ function stringify(props: {
99
124
  lines.push(itemStr);
100
125
  });
101
126
 
127
+ // Add missing element placeholders at the end for each [] error
128
+ if (hasMissingElements) {
129
+ const innerIndent = " ".repeat(tab + 1);
130
+ missingElementErrors.forEach((e, idx) => {
131
+ const errComment = ` // ❌ ${JSON.stringify([{ path: e.path, expected: e.expected, description: e.description }])}`;
132
+ const comma = idx < missingElementErrors.length - 1 ? "," : "";
133
+ lines.push(`${innerIndent}undefined${comma}${errComment}`);
134
+ });
135
+ }
136
+
102
137
  lines.push(`${indent}]`);
103
138
  return lines.join("\n");
104
139
  }
@@ -119,17 +154,33 @@ function stringify(props: {
119
154
  });
120
155
  }
121
156
 
122
- // Get existing entries (filter out undefined values from actual data)
123
- const existingEntries: [string, unknown][] = Object.entries(value).filter(
157
+ // Get all entries from the object (including undefined values that have errors)
158
+ const allEntries: [string, unknown][] = Object.entries(value);
159
+
160
+ // Split into defined and undefined entries
161
+ const definedEntries: [string, unknown][] = allEntries.filter(
124
162
  ([_, val]) => val !== undefined,
125
163
  );
164
+ const undefinedEntryKeys: Set<string> = new Set(
165
+ allEntries.filter(([_, val]) => val === undefined).map(([key]) => key),
166
+ );
126
167
 
127
- // Find missing properties that have validation errors
168
+ // Find missing properties that have validation errors (not in object at all)
128
169
  const missingKeys: string[] = getMissingProperties(path, value, errors);
129
170
 
130
- // Combine existing and missing properties
171
+ // Combine: defined entries + undefined entries with errors + missing properties
172
+ const undefinedKeysWithErrors: string[] = Array.from(
173
+ undefinedEntryKeys,
174
+ ).filter((key) => {
175
+ const propPath = Escaper.variable(key)
176
+ ? `${path}.${key}`
177
+ : `${path}[${JSON.stringify(key)}]`;
178
+ return errors.some(e => e.path.startsWith(propPath));
179
+ });
180
+
131
181
  const allKeys: string[] = [
132
- ...existingEntries.map(([key]) => key),
182
+ ...definedEntries.map(([key]) => key),
183
+ ...undefinedKeysWithErrors,
133
184
  ...missingKeys,
134
185
  ];
135
186
 
@@ -146,8 +197,11 @@ function stringify(props: {
146
197
  : `${path}[${JSON.stringify(key)}]`;
147
198
  const propIndent: string = " ".repeat(tab + 1);
148
199
 
149
- // Get the value (undefined for missing properties)
150
- const val: unknown = missingKeys.includes(key) ? undefined : (value as any)[key];
200
+ // Get the value (undefined for missing properties or undefined entries)
201
+ const val: unknown
202
+ = missingKeys.includes(key) || undefinedKeysWithErrors.includes(key)
203
+ ? undefined
204
+ : (value as any)[key];
151
205
 
152
206
  // Primitive property value (including undefined for missing properties)
153
207
  if (
@@ -157,10 +211,16 @@ function stringify(props: {
157
211
  || typeof val === "number"
158
212
  || typeof val === "string"
159
213
  ) {
160
- const propErrorComment: string = getErrorComment(propPath, errors, usedErrors);
161
- const valueStr: string = val === undefined
162
- ? `${propIndent}"${key}": undefined`
163
- : `${propIndent}"${key}": ${JSON.stringify(val)}`;
214
+ const propErrorComment: string = getErrorComment(
215
+ propPath,
216
+ errors,
217
+ usedErrors,
218
+ );
219
+ const keyStr: string = JSON.stringify(key);
220
+ const valueStr: string
221
+ = val === undefined
222
+ ? `${propIndent}${keyStr}: undefined`
223
+ : `${propIndent}${keyStr}: ${JSON.stringify(val)}`;
164
224
  const withComma: string
165
225
  = index < array.length - 1 ? `${valueStr},` : valueStr;
166
226
  const line: string = withComma + propErrorComment;
@@ -168,7 +228,7 @@ function stringify(props: {
168
228
  }
169
229
  // Complex property value (object or array)
170
230
  else {
171
- const keyLine: string = `${propIndent}"${key}": `;
231
+ const keyLine: string = `${propIndent}${JSON.stringify(key)}: `;
172
232
  let valStr: string = stringify({
173
233
  value: val,
174
234
  errors,
@@ -185,10 +245,10 @@ function stringify(props: {
185
245
  const lastLine: string = valLines[valLines.length - 1]!;
186
246
  const commentIndex: number = lastLine.indexOf(" //");
187
247
  if (commentIndex !== -1) {
188
- valLines[valLines.length - 1]
189
- = `${lastLine.slice(0, commentIndex)
190
- },${
191
- lastLine.slice(commentIndex)}`;
248
+ valLines[valLines.length - 1] = `${lastLine.slice(
249
+ 0,
250
+ commentIndex,
251
+ )},${lastLine.slice(commentIndex)}`;
192
252
  }
193
253
  else {
194
254
  valLines[valLines.length - 1] += ",";
@@ -241,8 +301,26 @@ function getErrorComment(
241
301
  }
242
302
 
243
303
  /**
244
- * Find missing properties that have validation errors but don't exist in the data
245
- * Returns array of property keys that should be displayed as undefined
304
+ * Check if there are missing array element errors (path ending with []) Returns
305
+ * an array of error objects, one per missing element
306
+ */
307
+ function getMissingArrayElementErrors(
308
+ path: string,
309
+ errors: IValidation.IError[],
310
+ usedErrors: Set<IValidation.IError>,
311
+ ): IValidation.IError[] {
312
+ const wildcardPath = `${path}[]`;
313
+ const missingErrors = errors.filter(e => e.path === wildcardPath);
314
+
315
+ // Mark these errors as used
316
+ missingErrors.forEach(e => usedErrors.add(e));
317
+
318
+ return missingErrors;
319
+ }
320
+
321
+ /**
322
+ * Find missing properties that have validation errors but don't exist in the
323
+ * data Returns array of property keys that should be displayed as undefined
246
324
  */
247
325
  function getMissingProperties(
248
326
  path: string,
@@ -266,24 +344,51 @@ function getMissingProperties(
266
344
  }
267
345
 
268
346
  /**
269
- * Extract direct child property key if errorPath is a direct child of parentPath
270
- * Returns null if not a direct child
347
+ * Extract direct child property key if errorPath is a direct child of
348
+ * parentPath Returns null if not a direct child
271
349
  *
272
350
  * Examples:
273
- * - extractDirectChildKey("$input", "$input.email") => "email"
274
- * - extractDirectChildKey("$input", "$input.user.email") => null (grandchild)
275
- * - extractDirectChildKey("$input.user", "$input.user.email") => "email"
276
- * - extractDirectChildKey("$input", "$input[0]") => null (array index, not object property)
351
+ *
352
+ * - ExtractDirectChildKey("$input", "$input.email") => "email"
353
+ * - ExtractDirectChildKey("$input", "$input.user.email") => null (grandchild)
354
+ * - ExtractDirectChildKey("$input.user", "$input.user.email") => "email"
355
+ * - ExtractDirectChildKey("$input", "$input[0]") => null (array index, not object
356
+ * property)
357
+ * - ExtractDirectChildKey("$input", "$input["foo-bar"]") => "foo-bar"
358
+ * - ExtractDirectChildKey("$input", "$input["foo"]["bar"]") => null (grandchild)
277
359
  */
278
- function extractDirectChildKey(parentPath: string, errorPath: string): string | null {
360
+ function extractDirectChildKey(
361
+ parentPath: string,
362
+ errorPath: string,
363
+ ): string | null {
279
364
  if (!errorPath.startsWith(parentPath)) {
280
365
  return null;
281
366
  }
282
367
 
283
368
  const suffix = errorPath.slice(parentPath.length);
284
369
 
285
- // Match ".propertyName" pattern (direct child property)
370
+ // Match ".propertyName" pattern (direct child property with dot notation)
286
371
  // Should not contain additional dots or brackets after the property name
287
- const match = suffix.match(/^\.([^.[\]]+)$/);
288
- return match !== null ? match[1]! : null;
372
+ const dotMatch = suffix.match(/^\.([^.[\]]+)$/);
373
+ if (dotMatch !== null) {
374
+ return dotMatch[1]!;
375
+ }
376
+
377
+ // Match '["key"]' pattern (direct child property with bracket notation)
378
+ // The key is a JSON-encoded string
379
+ const bracketMatch = suffix.match(/^\[("[^"\\]*(?:\\.[^"\\]*)*")\]$/);
380
+ if (bracketMatch !== null) {
381
+ try {
382
+ const parsed = JSON.parse(bracketMatch[1]!);
383
+ // Ensure it's a string key, not a number (array index)
384
+ if (typeof parsed === "string") {
385
+ return parsed;
386
+ }
387
+ }
388
+ catch {
389
+ // Invalid JSON, ignore
390
+ }
391
+ }
392
+
393
+ return null;
289
394
  }