@enactprotocol/shared 1.2.13 → 2.0.0

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 (134) hide show
  1. package/README.md +44 -0
  2. package/package.json +16 -58
  3. package/src/config.ts +476 -0
  4. package/src/constants.ts +36 -0
  5. package/src/execution/command.ts +314 -0
  6. package/src/execution/index.ts +73 -0
  7. package/src/execution/runtime.ts +308 -0
  8. package/src/execution/types.ts +379 -0
  9. package/src/execution/validation.ts +508 -0
  10. package/src/index.ts +237 -30
  11. package/src/manifest/index.ts +36 -0
  12. package/src/manifest/loader.ts +187 -0
  13. package/src/manifest/parser.ts +173 -0
  14. package/src/manifest/validator.ts +309 -0
  15. package/src/paths.ts +108 -0
  16. package/src/registry.ts +219 -0
  17. package/src/resolver.ts +345 -0
  18. package/src/types/index.ts +30 -0
  19. package/src/types/manifest.ts +255 -0
  20. package/src/types.ts +5 -188
  21. package/src/utils/fs.ts +281 -0
  22. package/src/utils/logger.ts +270 -59
  23. package/src/utils/version.ts +304 -36
  24. package/tests/config.test.ts +515 -0
  25. package/tests/execution/command.test.ts +317 -0
  26. package/tests/execution/validation.test.ts +384 -0
  27. package/tests/fixtures/invalid-tool.yaml +4 -0
  28. package/tests/fixtures/valid-tool.md +62 -0
  29. package/tests/fixtures/valid-tool.yaml +40 -0
  30. package/tests/index.test.ts +8 -0
  31. package/tests/manifest/loader.test.ts +291 -0
  32. package/tests/manifest/parser.test.ts +345 -0
  33. package/tests/manifest/validator.test.ts +394 -0
  34. package/tests/manifest-types.test.ts +358 -0
  35. package/tests/paths.test.ts +153 -0
  36. package/tests/registry.test.ts +231 -0
  37. package/tests/resolver.test.ts +272 -0
  38. package/tests/utils/fs.test.ts +388 -0
  39. package/tests/utils/logger.test.ts +480 -0
  40. package/tests/utils/version.test.ts +390 -0
  41. package/tsconfig.json +12 -0
  42. package/tsconfig.tsbuildinfo +1 -0
  43. package/dist/LocalToolResolver.d.ts +0 -84
  44. package/dist/LocalToolResolver.js +0 -353
  45. package/dist/api/enact-api.d.ts +0 -130
  46. package/dist/api/enact-api.js +0 -428
  47. package/dist/api/index.d.ts +0 -2
  48. package/dist/api/index.js +0 -2
  49. package/dist/api/types.d.ts +0 -103
  50. package/dist/api/types.js +0 -1
  51. package/dist/constants.d.ts +0 -7
  52. package/dist/constants.js +0 -10
  53. package/dist/core/DaggerExecutionProvider.d.ts +0 -169
  54. package/dist/core/DaggerExecutionProvider.js +0 -1029
  55. package/dist/core/DirectExecutionProvider.d.ts +0 -23
  56. package/dist/core/DirectExecutionProvider.js +0 -406
  57. package/dist/core/EnactCore.d.ts +0 -162
  58. package/dist/core/EnactCore.js +0 -597
  59. package/dist/core/NativeExecutionProvider.d.ts +0 -9
  60. package/dist/core/NativeExecutionProvider.js +0 -16
  61. package/dist/core/index.d.ts +0 -3
  62. package/dist/core/index.js +0 -3
  63. package/dist/exec/index.d.ts +0 -3
  64. package/dist/exec/index.js +0 -3
  65. package/dist/exec/logger.d.ts +0 -11
  66. package/dist/exec/logger.js +0 -57
  67. package/dist/exec/validate.d.ts +0 -5
  68. package/dist/exec/validate.js +0 -167
  69. package/dist/index.d.ts +0 -21
  70. package/dist/index.js +0 -25
  71. package/dist/lib/enact-direct.d.ts +0 -150
  72. package/dist/lib/enact-direct.js +0 -159
  73. package/dist/lib/index.d.ts +0 -1
  74. package/dist/lib/index.js +0 -1
  75. package/dist/security/index.d.ts +0 -3
  76. package/dist/security/index.js +0 -3
  77. package/dist/security/security.d.ts +0 -23
  78. package/dist/security/security.js +0 -137
  79. package/dist/security/sign.d.ts +0 -103
  80. package/dist/security/sign.js +0 -666
  81. package/dist/security/verification-enforcer.d.ts +0 -53
  82. package/dist/security/verification-enforcer.js +0 -204
  83. package/dist/services/McpCoreService.d.ts +0 -98
  84. package/dist/services/McpCoreService.js +0 -124
  85. package/dist/services/index.d.ts +0 -1
  86. package/dist/services/index.js +0 -1
  87. package/dist/types.d.ts +0 -132
  88. package/dist/types.js +0 -3
  89. package/dist/utils/config.d.ts +0 -111
  90. package/dist/utils/config.js +0 -342
  91. package/dist/utils/env-loader.d.ts +0 -54
  92. package/dist/utils/env-loader.js +0 -270
  93. package/dist/utils/help.d.ts +0 -36
  94. package/dist/utils/help.js +0 -248
  95. package/dist/utils/index.d.ts +0 -7
  96. package/dist/utils/index.js +0 -7
  97. package/dist/utils/logger.d.ts +0 -35
  98. package/dist/utils/logger.js +0 -75
  99. package/dist/utils/silent-monitor.d.ts +0 -67
  100. package/dist/utils/silent-monitor.js +0 -242
  101. package/dist/utils/timeout.d.ts +0 -5
  102. package/dist/utils/timeout.js +0 -23
  103. package/dist/utils/version.d.ts +0 -4
  104. package/dist/utils/version.js +0 -35
  105. package/dist/web/env-manager-server.d.ts +0 -29
  106. package/dist/web/env-manager-server.js +0 -367
  107. package/dist/web/index.d.ts +0 -1
  108. package/dist/web/index.js +0 -1
  109. package/src/LocalToolResolver.ts +0 -424
  110. package/src/api/enact-api.ts +0 -604
  111. package/src/api/index.ts +0 -2
  112. package/src/api/types.ts +0 -114
  113. package/src/core/DaggerExecutionProvider.ts +0 -1357
  114. package/src/core/DirectExecutionProvider.ts +0 -484
  115. package/src/core/EnactCore.ts +0 -847
  116. package/src/core/index.ts +0 -3
  117. package/src/exec/index.ts +0 -3
  118. package/src/exec/logger.ts +0 -63
  119. package/src/exec/validate.ts +0 -238
  120. package/src/lib/enact-direct.ts +0 -254
  121. package/src/lib/index.ts +0 -1
  122. package/src/services/McpCoreService.ts +0 -201
  123. package/src/services/index.ts +0 -1
  124. package/src/utils/config.ts +0 -438
  125. package/src/utils/env-loader.ts +0 -370
  126. package/src/utils/help.ts +0 -257
  127. package/src/utils/index.ts +0 -7
  128. package/src/utils/silent-monitor.ts +0 -328
  129. package/src/utils/timeout.ts +0 -26
  130. package/src/web/env-manager-server.ts +0 -465
  131. package/src/web/index.ts +0 -1
  132. package/src/web/static/app.js +0 -663
  133. package/src/web/static/index.html +0 -117
  134. package/src/web/static/style.css +0 -291
@@ -0,0 +1,508 @@
1
+ /**
2
+ * Input validation using JSON Schema
3
+ *
4
+ * Validates tool inputs against the manifest's inputSchema.
5
+ */
6
+
7
+ import type { JSONSchema7 } from "json-schema";
8
+ import type { InputValidationError, InputValidationResult } from "./types";
9
+
10
+ /**
11
+ * Validate inputs against a JSON Schema
12
+ *
13
+ * @param inputs - The inputs to validate
14
+ * @param schema - The JSON Schema to validate against
15
+ * @returns Validation result with errors and coerced values
16
+ */
17
+ export function validateInputs(
18
+ inputs: Record<string, unknown>,
19
+ schema: JSONSchema7 | undefined
20
+ ): InputValidationResult {
21
+ // If no schema, everything is valid
22
+ if (!schema) {
23
+ return { valid: true, errors: [], coercedValues: inputs };
24
+ }
25
+
26
+ const errors: InputValidationError[] = [];
27
+ const coercedValues: Record<string, unknown> = { ...inputs };
28
+
29
+ // Check schema type (should be object)
30
+ if (schema.type !== "object") {
31
+ return { valid: true, errors: [], coercedValues: inputs };
32
+ }
33
+
34
+ const properties = schema.properties || {};
35
+ const required = schema.required || [];
36
+
37
+ // Check required properties
38
+ for (const propName of required) {
39
+ if (inputs[propName] === undefined) {
40
+ errors.push({
41
+ path: `params.${propName}`,
42
+ message: `Missing required parameter: ${propName}`,
43
+ expected: "value",
44
+ });
45
+ }
46
+ }
47
+
48
+ // Validate each property
49
+ for (const [propName, propValue] of Object.entries(inputs)) {
50
+ const propSchema = properties[propName] as JSONSchema7 | undefined;
51
+
52
+ if (propSchema) {
53
+ const propErrors = validateProperty(propName, propValue, propSchema);
54
+ errors.push(...propErrors);
55
+
56
+ // Attempt type coercion
57
+ const coerced = coerceValue(propValue, propSchema);
58
+ if (coerced !== undefined) {
59
+ coercedValues[propName] = coerced;
60
+ }
61
+ }
62
+ }
63
+
64
+ // Check for additional properties if not allowed
65
+ if (schema.additionalProperties === false) {
66
+ for (const propName of Object.keys(inputs)) {
67
+ if (!properties[propName]) {
68
+ errors.push({
69
+ path: `params.${propName}`,
70
+ message: `Unknown parameter: ${propName}`,
71
+ });
72
+ }
73
+ }
74
+ }
75
+
76
+ return {
77
+ valid: errors.length === 0,
78
+ errors,
79
+ coercedValues,
80
+ };
81
+ }
82
+
83
+ /**
84
+ * Validate a single property against its schema
85
+ */
86
+ function validateProperty(
87
+ name: string,
88
+ value: unknown,
89
+ schema: JSONSchema7
90
+ ): InputValidationError[] {
91
+ const errors: InputValidationError[] = [];
92
+ const path = `params.${name}`;
93
+
94
+ // Type validation
95
+ if (schema.type) {
96
+ const typeValid = validateType(value, schema.type);
97
+ if (!typeValid) {
98
+ errors.push({
99
+ path,
100
+ message: `Invalid type for ${name}: expected ${schema.type}, got ${typeof value}`,
101
+ expected: String(schema.type),
102
+ actual: value,
103
+ });
104
+ return errors; // Skip further validation if type is wrong
105
+ }
106
+ }
107
+
108
+ // String validations
109
+ if (typeof value === "string") {
110
+ // minLength
111
+ if (schema.minLength !== undefined && value.length < schema.minLength) {
112
+ errors.push({
113
+ path,
114
+ message: `${name} must be at least ${schema.minLength} characters`,
115
+ expected: `minLength: ${schema.minLength}`,
116
+ actual: value,
117
+ });
118
+ }
119
+
120
+ // maxLength
121
+ if (schema.maxLength !== undefined && value.length > schema.maxLength) {
122
+ errors.push({
123
+ path,
124
+ message: `${name} must be at most ${schema.maxLength} characters`,
125
+ expected: `maxLength: ${schema.maxLength}`,
126
+ actual: value,
127
+ });
128
+ }
129
+
130
+ // pattern
131
+ if (schema.pattern) {
132
+ const regex = new RegExp(schema.pattern);
133
+ if (!regex.test(value)) {
134
+ errors.push({
135
+ path,
136
+ message: `${name} must match pattern: ${schema.pattern}`,
137
+ expected: `pattern: ${schema.pattern}`,
138
+ actual: value,
139
+ });
140
+ }
141
+ }
142
+
143
+ // format (basic support)
144
+ if (schema.format) {
145
+ const formatError = validateFormat(value, schema.format);
146
+ if (formatError) {
147
+ errors.push({
148
+ path,
149
+ message: `${name}: ${formatError}`,
150
+ expected: `format: ${schema.format}`,
151
+ actual: value,
152
+ });
153
+ }
154
+ }
155
+ }
156
+
157
+ // Number validations
158
+ if (typeof value === "number") {
159
+ // minimum
160
+ if (schema.minimum !== undefined && value < schema.minimum) {
161
+ errors.push({
162
+ path,
163
+ message: `${name} must be >= ${schema.minimum}`,
164
+ expected: `minimum: ${schema.minimum}`,
165
+ actual: value,
166
+ });
167
+ }
168
+
169
+ // maximum
170
+ if (schema.maximum !== undefined && value > schema.maximum) {
171
+ errors.push({
172
+ path,
173
+ message: `${name} must be <= ${schema.maximum}`,
174
+ expected: `maximum: ${schema.maximum}`,
175
+ actual: value,
176
+ });
177
+ }
178
+
179
+ // exclusiveMinimum
180
+ if (schema.exclusiveMinimum !== undefined && value <= schema.exclusiveMinimum) {
181
+ errors.push({
182
+ path,
183
+ message: `${name} must be > ${schema.exclusiveMinimum}`,
184
+ expected: `exclusiveMinimum: ${schema.exclusiveMinimum}`,
185
+ actual: value,
186
+ });
187
+ }
188
+
189
+ // exclusiveMaximum
190
+ if (schema.exclusiveMaximum !== undefined && value >= schema.exclusiveMaximum) {
191
+ errors.push({
192
+ path,
193
+ message: `${name} must be < ${schema.exclusiveMaximum}`,
194
+ expected: `exclusiveMaximum: ${schema.exclusiveMaximum}`,
195
+ actual: value,
196
+ });
197
+ }
198
+
199
+ // multipleOf
200
+ if (schema.multipleOf !== undefined && value % schema.multipleOf !== 0) {
201
+ errors.push({
202
+ path,
203
+ message: `${name} must be a multiple of ${schema.multipleOf}`,
204
+ expected: `multipleOf: ${schema.multipleOf}`,
205
+ actual: value,
206
+ });
207
+ }
208
+ }
209
+
210
+ // Enum validation
211
+ if (schema.enum && !schema.enum.includes(value as string | number | boolean | null)) {
212
+ errors.push({
213
+ path,
214
+ message: `${name} must be one of: ${schema.enum.join(", ")}`,
215
+ expected: `enum: [${schema.enum.join(", ")}]`,
216
+ actual: value,
217
+ });
218
+ }
219
+
220
+ // Const validation
221
+ if (schema.const !== undefined && value !== schema.const) {
222
+ errors.push({
223
+ path,
224
+ message: `${name} must be: ${schema.const}`,
225
+ expected: `const: ${schema.const}`,
226
+ actual: value,
227
+ });
228
+ }
229
+
230
+ // Array validations
231
+ if (Array.isArray(value)) {
232
+ // minItems
233
+ if (schema.minItems !== undefined && value.length < schema.minItems) {
234
+ errors.push({
235
+ path,
236
+ message: `${name} must have at least ${schema.minItems} items`,
237
+ expected: `minItems: ${schema.minItems}`,
238
+ actual: value,
239
+ });
240
+ }
241
+
242
+ // maxItems
243
+ if (schema.maxItems !== undefined && value.length > schema.maxItems) {
244
+ errors.push({
245
+ path,
246
+ message: `${name} must have at most ${schema.maxItems} items`,
247
+ expected: `maxItems: ${schema.maxItems}`,
248
+ actual: value,
249
+ });
250
+ }
251
+
252
+ // uniqueItems
253
+ if (schema.uniqueItems) {
254
+ const seen = new Set();
255
+ const hasDuplicates = value.some((item) => {
256
+ const key = JSON.stringify(item);
257
+ if (seen.has(key)) return true;
258
+ seen.add(key);
259
+ return false;
260
+ });
261
+ if (hasDuplicates) {
262
+ errors.push({
263
+ path,
264
+ message: `${name} must contain unique items`,
265
+ expected: "uniqueItems: true",
266
+ actual: value,
267
+ });
268
+ }
269
+ }
270
+ }
271
+
272
+ return errors;
273
+ }
274
+
275
+ /**
276
+ * Validate a value matches the expected type
277
+ */
278
+ function validateType(value: unknown, type: JSONSchema7["type"]): boolean {
279
+ if (type === undefined) return true;
280
+
281
+ // Handle array of types
282
+ if (Array.isArray(type)) {
283
+ return type.some((t) => validateType(value, t));
284
+ }
285
+
286
+ switch (type) {
287
+ case "string":
288
+ return typeof value === "string";
289
+ case "number":
290
+ return typeof value === "number" && !Number.isNaN(value);
291
+ case "integer":
292
+ return typeof value === "number" && Number.isInteger(value);
293
+ case "boolean":
294
+ return typeof value === "boolean";
295
+ case "array":
296
+ return Array.isArray(value);
297
+ case "object":
298
+ return typeof value === "object" && value !== null && !Array.isArray(value);
299
+ case "null":
300
+ return value === null;
301
+ default:
302
+ return true;
303
+ }
304
+ }
305
+
306
+ /**
307
+ * Validate string format
308
+ */
309
+ function validateFormat(value: string, format: string): string | null {
310
+ switch (format) {
311
+ case "email": {
312
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
313
+ if (!emailRegex.test(value)) {
314
+ return "Invalid email format";
315
+ }
316
+ break;
317
+ }
318
+
319
+ case "uri":
320
+ case "url": {
321
+ try {
322
+ new URL(value);
323
+ } catch {
324
+ return "Invalid URL format";
325
+ }
326
+ break;
327
+ }
328
+
329
+ case "date": {
330
+ const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
331
+ if (!dateRegex.test(value) || Number.isNaN(Date.parse(value))) {
332
+ return "Invalid date format (expected YYYY-MM-DD)";
333
+ }
334
+ break;
335
+ }
336
+
337
+ case "date-time": {
338
+ if (Number.isNaN(Date.parse(value))) {
339
+ return "Invalid date-time format";
340
+ }
341
+ break;
342
+ }
343
+
344
+ case "time": {
345
+ const timeRegex = /^\d{2}:\d{2}(:\d{2})?$/;
346
+ if (!timeRegex.test(value)) {
347
+ return "Invalid time format (expected HH:MM or HH:MM:SS)";
348
+ }
349
+ break;
350
+ }
351
+
352
+ case "uuid": {
353
+ const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
354
+ if (!uuidRegex.test(value)) {
355
+ return "Invalid UUID format";
356
+ }
357
+ break;
358
+ }
359
+
360
+ case "hostname": {
361
+ const hostnameRegex =
362
+ /^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
363
+ if (!hostnameRegex.test(value)) {
364
+ return "Invalid hostname format";
365
+ }
366
+ break;
367
+ }
368
+
369
+ case "ipv4": {
370
+ const ipv4Regex =
371
+ /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
372
+ if (!ipv4Regex.test(value)) {
373
+ return "Invalid IPv4 format";
374
+ }
375
+ break;
376
+ }
377
+
378
+ // Note: Add more formats as needed
379
+ }
380
+
381
+ return null;
382
+ }
383
+
384
+ /**
385
+ * Attempt to coerce a value to match the schema type
386
+ */
387
+ function coerceValue(value: unknown, schema: JSONSchema7): unknown {
388
+ if (value === undefined || value === null) {
389
+ return schema.default;
390
+ }
391
+
392
+ const type = schema.type;
393
+ if (!type || Array.isArray(type)) {
394
+ return value;
395
+ }
396
+
397
+ // String coercion
398
+ if (type === "string" && typeof value !== "string") {
399
+ return String(value);
400
+ }
401
+
402
+ // Number coercion
403
+ if ((type === "number" || type === "integer") && typeof value === "string") {
404
+ const parsed = Number(value);
405
+ if (!Number.isNaN(parsed)) {
406
+ if (type === "integer") {
407
+ return Math.floor(parsed);
408
+ }
409
+ return parsed;
410
+ }
411
+ }
412
+
413
+ // Boolean coercion
414
+ if (type === "boolean" && typeof value === "string") {
415
+ if (value.toLowerCase() === "true") return true;
416
+ if (value.toLowerCase() === "false") return false;
417
+ }
418
+
419
+ return value;
420
+ }
421
+
422
+ /**
423
+ * Apply default values from schema to inputs
424
+ *
425
+ * @param inputs - Current inputs
426
+ * @param schema - Input schema with defaults
427
+ * @returns Inputs with defaults applied
428
+ */
429
+ export function applyDefaults(
430
+ inputs: Record<string, unknown>,
431
+ schema: JSONSchema7 | undefined
432
+ ): Record<string, unknown> {
433
+ if (!schema || schema.type !== "object" || !schema.properties) {
434
+ return inputs;
435
+ }
436
+
437
+ const result = { ...inputs };
438
+
439
+ for (const [propName, propSchema] of Object.entries(schema.properties)) {
440
+ if (result[propName] === undefined) {
441
+ const prop = propSchema as JSONSchema7;
442
+ if (prop.default !== undefined) {
443
+ result[propName] = prop.default;
444
+ }
445
+ }
446
+ }
447
+
448
+ return result;
449
+ }
450
+
451
+ /**
452
+ * Get the list of required parameters from a schema
453
+ *
454
+ * @param schema - Input schema
455
+ * @returns Array of required parameter names
456
+ */
457
+ export function getRequiredParams(schema: JSONSchema7 | undefined): string[] {
458
+ if (!schema || !schema.required) {
459
+ return [];
460
+ }
461
+ return [...schema.required];
462
+ }
463
+
464
+ /**
465
+ * Get parameter info from schema for help/documentation
466
+ *
467
+ * @param schema - Input schema
468
+ * @returns Map of parameter name to info
469
+ */
470
+ export function getParamInfo(
471
+ schema: JSONSchema7 | undefined
472
+ ): Map<
473
+ string,
474
+ { type: string; description?: string | undefined; required: boolean; default?: unknown }
475
+ > {
476
+ const info = new Map<
477
+ string,
478
+ { type: string; description?: string | undefined; required: boolean; default?: unknown }
479
+ >();
480
+
481
+ if (!schema || schema.type !== "object" || !schema.properties) {
482
+ return info;
483
+ }
484
+
485
+ const required = new Set(schema.required || []);
486
+
487
+ for (const [propName, propSchema] of Object.entries(schema.properties)) {
488
+ const prop = propSchema as JSONSchema7;
489
+ const entry: {
490
+ type: string;
491
+ description?: string | undefined;
492
+ required: boolean;
493
+ default?: unknown;
494
+ } = {
495
+ type: Array.isArray(prop.type) ? prop.type.join(" | ") : prop.type || "any",
496
+ required: required.has(propName),
497
+ };
498
+ if (prop.description !== undefined) {
499
+ entry.description = prop.description;
500
+ }
501
+ if (prop.default !== undefined) {
502
+ entry.default = prop.default;
503
+ }
504
+ info.set(propName, entry);
505
+ }
506
+
507
+ return info;
508
+ }