@agentica/core 0.36.0 → 0.36.2

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.
@@ -0,0 +1,251 @@
1
+ import type { IValidation } from "typia";
2
+
3
+ import { Escaper } from "typia/lib/utils/Escaper";
4
+
5
+ export function stringifyValidateFailure(
6
+ failure: IValidation.IFailure,
7
+ ): string {
8
+ return stringify({
9
+ value: failure.data,
10
+ errors: failure.errors,
11
+ path: "$input",
12
+ tab: 0,
13
+ inArray: false,
14
+ inToJson: false,
15
+ });
16
+ }
17
+
18
+ function stringify(props: {
19
+ value: unknown;
20
+ errors: IValidation.IError[];
21
+ path: string;
22
+ tab: number;
23
+ inArray: boolean;
24
+ inToJson: boolean;
25
+ }): string {
26
+ const { value, errors, path, tab, inArray, inToJson } = props;
27
+ const indent: string = " ".repeat(tab);
28
+ const errorComment: string = getErrorComment(path, errors);
29
+
30
+ // Handle undefined in arrays
31
+ if (inArray && value === undefined) {
32
+ return `${indent}undefined${errorComment}`;
33
+ }
34
+
35
+ // Array
36
+ if (Array.isArray(value)) {
37
+ if (value.length === 0) {
38
+ return `${indent}[]${errorComment}`;
39
+ }
40
+
41
+ const lines: string[] = [];
42
+ lines.push(`${indent}[${errorComment}`);
43
+
44
+ value.forEach((item: unknown, index: number) => {
45
+ const itemPath: string = `${path}[${index}]`;
46
+ let itemStr: string = stringify({
47
+ value: item,
48
+ errors,
49
+ path: itemPath,
50
+ tab: tab + 1,
51
+ inArray: true,
52
+ inToJson: false,
53
+ });
54
+ // Add comma before the error comment if not the last element
55
+ if (index < value.length - 1) {
56
+ const itemLines: string[] = itemStr.split("\n");
57
+ const lastLine: string = itemLines[itemLines.length - 1]!;
58
+ const commentIndex: number = lastLine.indexOf(" //");
59
+ if (commentIndex !== -1) {
60
+ itemLines[itemLines.length - 1]
61
+ = `${lastLine.slice(0, commentIndex)
62
+ },${
63
+ lastLine.slice(commentIndex)}`;
64
+ }
65
+ else {
66
+ itemLines[itemLines.length - 1] += ",";
67
+ }
68
+ itemStr = itemLines.join("\n");
69
+ }
70
+ lines.push(itemStr);
71
+ });
72
+
73
+ lines.push(`${indent}]`);
74
+ return lines.join("\n");
75
+ }
76
+
77
+ // Object
78
+ if (typeof value === "object" && value !== null) {
79
+ // Check for toJSON method
80
+ if (!inToJson && typeof (value as any).toJSON === "function") {
81
+ const jsonValue: unknown = (value as any).toJSON();
82
+ return stringify({
83
+ value: jsonValue,
84
+ errors,
85
+ path,
86
+ tab,
87
+ inArray,
88
+ inToJson: true,
89
+ });
90
+ }
91
+
92
+ // Get existing entries (filter out undefined values from actual data)
93
+ const existingEntries: [string, unknown][] = Object.entries(value).filter(
94
+ ([_, val]) => val !== undefined,
95
+ );
96
+
97
+ // Find missing properties that have validation errors
98
+ const missingKeys: string[] = getMissingProperties(path, value, errors);
99
+
100
+ // Combine existing and missing properties
101
+ const allKeys: string[] = [
102
+ ...existingEntries.map(([key]) => key),
103
+ ...missingKeys,
104
+ ];
105
+
106
+ if (allKeys.length === 0) {
107
+ return `${indent}{}${errorComment}`;
108
+ }
109
+
110
+ const lines: string[] = [];
111
+ lines.push(`${indent}{${errorComment}`);
112
+
113
+ allKeys.forEach((key, index, array) => {
114
+ const propPath: string = Escaper.variable(key)
115
+ ? `${path}.${key}`
116
+ : `${path}[${JSON.stringify(key)}]`;
117
+ const propIndent: string = " ".repeat(tab + 1);
118
+
119
+ // Get the value (undefined for missing properties)
120
+ const val: unknown = missingKeys.includes(key) ? undefined : (value as any)[key];
121
+
122
+ // Primitive property value (including undefined for missing properties)
123
+ if (
124
+ val === undefined
125
+ || val === null
126
+ || typeof val === "boolean"
127
+ || typeof val === "number"
128
+ || typeof val === "string"
129
+ ) {
130
+ const propErrorComment: string = getErrorComment(propPath, errors);
131
+ const valueStr: string = val === undefined
132
+ ? `${propIndent}"${key}": undefined`
133
+ : `${propIndent}"${key}": ${JSON.stringify(val)}`;
134
+ const withComma: string
135
+ = index < array.length - 1 ? `${valueStr},` : valueStr;
136
+ const line: string = withComma + propErrorComment;
137
+ lines.push(line);
138
+ }
139
+ // Complex property value (object or array)
140
+ else {
141
+ const keyLine: string = `${propIndent}"${key}": `;
142
+ let valStr: string = stringify({
143
+ value: val,
144
+ errors,
145
+ path: propPath,
146
+ tab: tab + 1,
147
+ inArray: false,
148
+ inToJson: false,
149
+ });
150
+ const valStrWithoutIndent: string = valStr.trimStart();
151
+ // Add comma before the error comment if not the last property
152
+ if (index < array.length - 1) {
153
+ const valLines: string[] = valStrWithoutIndent.split("\n");
154
+ const lastLine: string = valLines[valLines.length - 1]!;
155
+ const commentIndex: number = lastLine.indexOf(" //");
156
+ if (commentIndex !== -1) {
157
+ valLines[valLines.length - 1]
158
+ = `${lastLine.slice(0, commentIndex)
159
+ },${
160
+ lastLine.slice(commentIndex)}`;
161
+ }
162
+ else {
163
+ valLines[valLines.length - 1] += ",";
164
+ }
165
+ valStr = valLines.join("\n");
166
+ }
167
+ else {
168
+ valStr = valStrWithoutIndent;
169
+ }
170
+ const combined: string = keyLine + valStr;
171
+ lines.push(combined);
172
+ }
173
+ });
174
+
175
+ lines.push(`${indent}}`);
176
+ return lines.join("\n");
177
+ }
178
+
179
+ // Primitive types (null, boolean, number, string, undefined, etc.)
180
+ const valStr: string
181
+ = value === undefined
182
+ ? "undefined"
183
+ : (JSON.stringify(value) ?? String(value));
184
+ return `${indent}${valStr}${errorComment}`;
185
+ }
186
+
187
+ /** Get error comment for a given path */
188
+ function getErrorComment(path: string, errors: IValidation.IError[]): string {
189
+ const pathErrors: IValidation.IError[] = errors.filter(
190
+ (e: IValidation.IError) => e.path === path,
191
+ );
192
+ if (pathErrors.length === 0) {
193
+ return "";
194
+ }
195
+
196
+ return ` // ❌ ${JSON.stringify(
197
+ pathErrors.map(e => ({
198
+ path: e.path,
199
+ expected: e.expected,
200
+ description: e.description,
201
+ })),
202
+ )}`;
203
+ }
204
+
205
+ /**
206
+ * Find missing properties that have validation errors but don't exist in the data
207
+ * Returns array of property keys that should be displayed as undefined
208
+ */
209
+ function getMissingProperties(
210
+ path: string,
211
+ value: object,
212
+ errors: IValidation.IError[],
213
+ ): string[] {
214
+ const missingKeys: Set<string> = new Set();
215
+
216
+ for (const e of errors) {
217
+ // Check if error.path is a direct child of current path
218
+ const childKey = extractDirectChildKey(path, e.path);
219
+ if (childKey !== null) {
220
+ // Check if this property actually exists in the value
221
+ if (!(childKey in value)) {
222
+ missingKeys.add(childKey);
223
+ }
224
+ }
225
+ }
226
+
227
+ return Array.from(missingKeys);
228
+ }
229
+
230
+ /**
231
+ * Extract direct child property key if errorPath is a direct child of parentPath
232
+ * Returns null if not a direct child
233
+ *
234
+ * Examples:
235
+ * - extractDirectChildKey("$input", "$input.email") => "email"
236
+ * - extractDirectChildKey("$input", "$input.user.email") => null (grandchild)
237
+ * - extractDirectChildKey("$input.user", "$input.user.email") => "email"
238
+ * - extractDirectChildKey("$input", "$input[0]") => null (array index, not object property)
239
+ */
240
+ function extractDirectChildKey(parentPath: string, errorPath: string): string | null {
241
+ if (!errorPath.startsWith(parentPath)) {
242
+ return null;
243
+ }
244
+
245
+ const suffix = errorPath.slice(parentPath.length);
246
+
247
+ // Match ".propertyName" pattern (direct child property)
248
+ // Should not contain additional dots or brackets after the property name
249
+ const match = suffix.match(/^\.([^.[\]]+)$/);
250
+ return match !== null ? match[1]! : null;
251
+ }