@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.
- package/README.md +44 -0
- package/package.json +16 -58
- package/src/config.ts +476 -0
- package/src/constants.ts +36 -0
- package/src/execution/command.ts +314 -0
- package/src/execution/index.ts +73 -0
- package/src/execution/runtime.ts +308 -0
- package/src/execution/types.ts +379 -0
- package/src/execution/validation.ts +508 -0
- package/src/index.ts +237 -30
- package/src/manifest/index.ts +36 -0
- package/src/manifest/loader.ts +187 -0
- package/src/manifest/parser.ts +173 -0
- package/src/manifest/validator.ts +309 -0
- package/src/paths.ts +108 -0
- package/src/registry.ts +219 -0
- package/src/resolver.ts +345 -0
- package/src/types/index.ts +30 -0
- package/src/types/manifest.ts +255 -0
- package/src/types.ts +5 -188
- package/src/utils/fs.ts +281 -0
- package/src/utils/logger.ts +270 -59
- package/src/utils/version.ts +304 -36
- package/tests/config.test.ts +515 -0
- package/tests/execution/command.test.ts +317 -0
- package/tests/execution/validation.test.ts +384 -0
- package/tests/fixtures/invalid-tool.yaml +4 -0
- package/tests/fixtures/valid-tool.md +62 -0
- package/tests/fixtures/valid-tool.yaml +40 -0
- package/tests/index.test.ts +8 -0
- package/tests/manifest/loader.test.ts +291 -0
- package/tests/manifest/parser.test.ts +345 -0
- package/tests/manifest/validator.test.ts +394 -0
- package/tests/manifest-types.test.ts +358 -0
- package/tests/paths.test.ts +153 -0
- package/tests/registry.test.ts +231 -0
- package/tests/resolver.test.ts +272 -0
- package/tests/utils/fs.test.ts +388 -0
- package/tests/utils/logger.test.ts +480 -0
- package/tests/utils/version.test.ts +390 -0
- package/tsconfig.json +12 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/dist/LocalToolResolver.d.ts +0 -84
- package/dist/LocalToolResolver.js +0 -353
- package/dist/api/enact-api.d.ts +0 -130
- package/dist/api/enact-api.js +0 -428
- package/dist/api/index.d.ts +0 -2
- package/dist/api/index.js +0 -2
- package/dist/api/types.d.ts +0 -103
- package/dist/api/types.js +0 -1
- package/dist/constants.d.ts +0 -7
- package/dist/constants.js +0 -10
- package/dist/core/DaggerExecutionProvider.d.ts +0 -169
- package/dist/core/DaggerExecutionProvider.js +0 -1029
- package/dist/core/DirectExecutionProvider.d.ts +0 -23
- package/dist/core/DirectExecutionProvider.js +0 -406
- package/dist/core/EnactCore.d.ts +0 -162
- package/dist/core/EnactCore.js +0 -597
- package/dist/core/NativeExecutionProvider.d.ts +0 -9
- package/dist/core/NativeExecutionProvider.js +0 -16
- package/dist/core/index.d.ts +0 -3
- package/dist/core/index.js +0 -3
- package/dist/exec/index.d.ts +0 -3
- package/dist/exec/index.js +0 -3
- package/dist/exec/logger.d.ts +0 -11
- package/dist/exec/logger.js +0 -57
- package/dist/exec/validate.d.ts +0 -5
- package/dist/exec/validate.js +0 -167
- package/dist/index.d.ts +0 -21
- package/dist/index.js +0 -25
- package/dist/lib/enact-direct.d.ts +0 -150
- package/dist/lib/enact-direct.js +0 -159
- package/dist/lib/index.d.ts +0 -1
- package/dist/lib/index.js +0 -1
- package/dist/security/index.d.ts +0 -3
- package/dist/security/index.js +0 -3
- package/dist/security/security.d.ts +0 -23
- package/dist/security/security.js +0 -137
- package/dist/security/sign.d.ts +0 -103
- package/dist/security/sign.js +0 -666
- package/dist/security/verification-enforcer.d.ts +0 -53
- package/dist/security/verification-enforcer.js +0 -204
- package/dist/services/McpCoreService.d.ts +0 -98
- package/dist/services/McpCoreService.js +0 -124
- package/dist/services/index.d.ts +0 -1
- package/dist/services/index.js +0 -1
- package/dist/types.d.ts +0 -132
- package/dist/types.js +0 -3
- package/dist/utils/config.d.ts +0 -111
- package/dist/utils/config.js +0 -342
- package/dist/utils/env-loader.d.ts +0 -54
- package/dist/utils/env-loader.js +0 -270
- package/dist/utils/help.d.ts +0 -36
- package/dist/utils/help.js +0 -248
- package/dist/utils/index.d.ts +0 -7
- package/dist/utils/index.js +0 -7
- package/dist/utils/logger.d.ts +0 -35
- package/dist/utils/logger.js +0 -75
- package/dist/utils/silent-monitor.d.ts +0 -67
- package/dist/utils/silent-monitor.js +0 -242
- package/dist/utils/timeout.d.ts +0 -5
- package/dist/utils/timeout.js +0 -23
- package/dist/utils/version.d.ts +0 -4
- package/dist/utils/version.js +0 -35
- package/dist/web/env-manager-server.d.ts +0 -29
- package/dist/web/env-manager-server.js +0 -367
- package/dist/web/index.d.ts +0 -1
- package/dist/web/index.js +0 -1
- package/src/LocalToolResolver.ts +0 -424
- package/src/api/enact-api.ts +0 -604
- package/src/api/index.ts +0 -2
- package/src/api/types.ts +0 -114
- package/src/core/DaggerExecutionProvider.ts +0 -1357
- package/src/core/DirectExecutionProvider.ts +0 -484
- package/src/core/EnactCore.ts +0 -847
- package/src/core/index.ts +0 -3
- package/src/exec/index.ts +0 -3
- package/src/exec/logger.ts +0 -63
- package/src/exec/validate.ts +0 -238
- package/src/lib/enact-direct.ts +0 -254
- package/src/lib/index.ts +0 -1
- package/src/services/McpCoreService.ts +0 -201
- package/src/services/index.ts +0 -1
- package/src/utils/config.ts +0 -438
- package/src/utils/env-loader.ts +0 -370
- package/src/utils/help.ts +0 -257
- package/src/utils/index.ts +0 -7
- package/src/utils/silent-monitor.ts +0 -328
- package/src/utils/timeout.ts +0 -26
- package/src/web/env-manager-server.ts +0 -465
- package/src/web/index.ts +0 -1
- package/src/web/static/app.js +0 -663
- package/src/web/static/index.html +0 -117
- 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
|
+
}
|