get-widget-requirements-v1.1.0.js•24.2 kB
#!/usr/bin/env node
/**
* Widget Requirements Tool v1.1.0 - FAIL-FAST RELIABILITY
* Provides precise API requirements for selected widgets with strict validation
* @version 1.1.0 (January 20, 2025)
* @status ENHANCED - Fail-fast validation with comprehensive error reporting
* @reference JIT workflow step 3 of 7
* @enhancement Strict validation, no fallbacks, detailed troubleshooting
*/
export class WidgetRequirementsToolV110 {
constructor() {
this.processingStartTime = null;
this.validationErrors = [];
this.requirementsDatabase = this.initializeRequirementsDatabase();
this.supportedWidgetTypes = this.getSupportedWidgetTypes();
}
/**
* Main requirements entry point with FAIL-FAST validation
* @param {Object} input - Contains selectedWidgets (array or object with widget types)
* @returns {Object} Precise API requirements for selected widgets OR detailed error
*/
async getWidgetRequirements(input) {
this.processingStartTime = new Date();
this.validationErrors = [];
try {
console.error('[WIDGET_REQUIREMENTS_V110] Starting fail-fast validation');
// FAIL-FAST VALIDATION PHASE
const validationResult = this.validateSelectedWidgetsComprehensively(input);
if (!validationResult.valid) {
throw new Error(`Widget Requirements validation failed:\n\n${validationResult.errors.join('\n\n')}\n\nReceived input: ${JSON.stringify(input, null, 2)}`);
}
const { normalizedWidgetTypes } = validationResult;
console.error(`[WIDGET_REQUIREMENTS_V110] Validation passed - Processing ${normalizedWidgetTypes.length} widget types`);
console.error(`[WIDGET_REQUIREMENTS_V110] Widget types: ${normalizedWidgetTypes.join(', ')}`);
// REQUIREMENTS GENERATION PHASE
const specificRequirements = this.getSpecificRequirements(normalizedWidgetTypes);
const targetedExamples = this.generateTargetedExamples(normalizedWidgetTypes);
const validationRules = this.generateValidationChecklist(normalizedWidgetTypes);
const structureGuidance = this.generateStructureGuidance(normalizedWidgetTypes);
console.error(`[WIDGET_REQUIREMENTS_V110] Success - Generated requirements for ${normalizedWidgetTypes.length} widgets`);
return {
success: true,
data: {
selectedWidgetTypes: normalizedWidgetTypes,
specificRequirements: specificRequirements,
targetedExamples: targetedExamples,
validationRules: validationRules,
structureGuidance: structureGuidance,
jitGuidance: {
approach: "Fail-fast Just-In-Time delivery with strict validation",
principle: "Only provide requirements for validated widget types",
benefit: "Precise, reliable requirements with comprehensive error handling",
nextStep: "Use these requirements to structure your lesson data in validate_lesson_data",
v110Enhancement: "Fail-fast validation ensures all widget types are supported and properly formatted"
}
},
metadata: {
version: "1.1.0",
timestamp: new Date().toISOString(),
processingTime: Date.now() - this.processingStartTime.getTime(),
widgetTypesProcessed: normalizedWidgetTypes.length,
failFastValidation: "passed"
}
};
} catch (error) {
console.error('[WIDGET_REQUIREMENTS_V110] FAIL-FAST ERROR:', error.message);
return {
success: false,
error: {
code: 'WIDGET_REQUIREMENTS_VALIDATION_FAILED',
message: error.message,
timestamp: new Date().toISOString(),
processingTime: Date.now() - this.processingStartTime.getTime(),
failFast: true,
developmentMode: true,
troubleshooting: {
requiredInputStructure: {
selectedWidgets: "object or array containing widget type information"
},
supportedInputFormats: [
"Array of widget objects: [{ type: 'head-1', count: 1 }, ...]",
"Array of strings: ['head-1', 'text-1', 'quiz-1']",
"Object with selectedWidgetTypes array: { selectedWidgetTypes: [...] }",
"Object with widgets array: { widgets: [...] }",
"Analysis result format: { data: { selectedWidgetTypes: [...] } }"
],
supportedWidgetTypes: this.supportedWidgetTypes,
validationChecks: [
"selectedWidgets must exist and not be null/undefined",
"Must contain at least one valid widget type",
"All widget types must be from supported list",
"Widget type format must be valid (e.g., 'head-1', not 'head')"
],
commonIssues: [
"Missing selectedWidgets parameter",
"Empty widget array or object",
"Unsupported widget type names",
"Incorrect widget type format",
"Nested data structure not properly accessed"
],
debugSteps: [
"1. Check if input.selectedWidgets exists",
"2. Verify widget types are from supported list",
"3. Check widget type format (type-number pattern)",
"4. Ensure array/object is not empty",
"5. Verify data structure matches expected format"
]
}
}
};
}
}
/**
* COMPREHENSIVE FAIL-FAST VALIDATION for selectedWidgets
* No fallbacks - strict validation with detailed error reporting
*/
validateSelectedWidgetsComprehensively(input) {
const errors = [];
// Basic input structure validation
if (!input || typeof input !== 'object') {
errors.push("❌ INPUT STRUCTURE ERROR:\nInput must be an object containing selectedWidgets.\nReceived: " + typeof input + "\nRequired format: { selectedWidgets: [...] }");
return { valid: false, errors };
}
// selectedWidgets existence validation
if (!input.selectedWidgets) {
errors.push("❌ MISSING SELECTEDWIDGETS ERROR:\nselectedWidgets parameter is required.\nThis should come from the analyze_content_for_widgets tool output.\nRequired format: { selectedWidgets: [...] }");
return { valid: false, errors };
}
if (input.selectedWidgets === null || input.selectedWidgets === undefined) {
errors.push("❌ NULL SELECTEDWIDGETS ERROR:\nselectedWidgets cannot be null or undefined.\nEnsure analyze_content_for_widgets completed successfully.\nCheck previous tool output for errors.");
return { valid: false, errors };
}
// Extract and validate widget types
const extractionResult = this.extractAndValidateWidgetTypes(input.selectedWidgets);
if (!extractionResult.valid) {
errors.push(...extractionResult.errors);
return { valid: false, errors };
}
const { normalizedWidgetTypes } = extractionResult;
// Validate extracted widget types
const widgetValidationResult = this.validateIndividualWidgetTypes(normalizedWidgetTypes);
if (!widgetValidationResult.valid) {
errors.push(...widgetValidationResult.errors);
return { valid: false, errors };
}
console.error(`[WIDGET_REQUIREMENTS_V110] Validation passed - ${normalizedWidgetTypes.length} valid widget types`);
return { valid: true, normalizedWidgetTypes, errors: [] };
}
/**
* EXTRACT AND VALIDATE widget types from various input formats
*/
extractAndValidateWidgetTypes(selectedWidgets) {
const errors = [];
let widgetTypes = [];
try {
// Handle array format
if (Array.isArray(selectedWidgets)) {
if (selectedWidgets.length === 0) {
errors.push("❌ EMPTY ARRAY ERROR:\nselectedWidgets array is empty.\nEnsure analyze_content_for_widgets generated widget recommendations.\nArray should contain widget type objects or strings.");
return { valid: false, errors };
}
// Array of widget objects: [{ type: "head-1", ... }, ...]
if (selectedWidgets.every(item => typeof item === 'object' && item.type)) {
widgetTypes = selectedWidgets.map(item => item.type);
}
// Array of strings: ["head-1", "text-1", ...]
else if (selectedWidgets.every(item => typeof item === 'string')) {
widgetTypes = selectedWidgets;
}
// Mixed or invalid array
else {
errors.push(`❌ INVALID ARRAY FORMAT ERROR:\nselectedWidgets array contains invalid items.\nExpected: Array of objects with 'type' property OR array of strings\nReceived: ${selectedWidgets.map(item => typeof item).join(', ')}\nFirst item: ${JSON.stringify(selectedWidgets[0], null, 2)}`);
return { valid: false, errors };
}
}
// Handle object format
else if (typeof selectedWidgets === 'object') {
// Check for nested data structure (from analyze_content_for_widgets)
if (selectedWidgets.data && selectedWidgets.data.selectedWidgetTypes) {
if (!Array.isArray(selectedWidgets.data.selectedWidgetTypes)) {
errors.push("❌ NESTED DATA FORMAT ERROR:\nselectedWidgets.data.selectedWidgetTypes must be an array.\nThis appears to be output from analyze_content_for_widgets tool.\nCheck if the tool completed successfully.");
return { valid: false, errors };
}
const nestedTypes = selectedWidgets.data.selectedWidgetTypes;
if (nestedTypes.length === 0) {
errors.push("❌ EMPTY NESTED ARRAY ERROR:\nselectedWidgets.data.selectedWidgetTypes is empty.\nanalyze_content_for_widgets should have generated widget recommendations.\nCheck previous tool output for errors.");
return { valid: false, errors };
}
// Extract type from nested objects
widgetTypes = nestedTypes.map(item => {
if (typeof item === 'object' && item.type) {
return item.type;
} else if (typeof item === 'string') {
return item;
} else {
throw new Error(`Invalid widget type format in nested data: ${JSON.stringify(item)}`);
}
});
}
// Direct selectedWidgetTypes property
else if (selectedWidgets.selectedWidgetTypes) {
if (!Array.isArray(selectedWidgets.selectedWidgetTypes)) {
errors.push("❌ SELECTEDWIDGETTYPES FORMAT ERROR:\nselectedWidgets.selectedWidgetTypes must be an array.\nReceived: " + typeof selectedWidgets.selectedWidgetTypes);
return { valid: false, errors };
}
widgetTypes = selectedWidgets.selectedWidgetTypes.map(item => {
if (typeof item === 'object' && item.type) {
return item.type;
} else if (typeof item === 'string') {
return item;
} else {
throw new Error(`Invalid widget type format: ${JSON.stringify(item)}`);
}
});
}
// widgets property
else if (selectedWidgets.widgets) {
if (!Array.isArray(selectedWidgets.widgets)) {
errors.push("❌ WIDGETS PROPERTY FORMAT ERROR:\nselectedWidgets.widgets must be an array.\nReceived: " + typeof selectedWidgets.widgets);
return { valid: false, errors };
}
widgetTypes = selectedWidgets.widgets.map(item => {
if (typeof item === 'object' && item.type) {
return item.type;
} else if (typeof item === 'string') {
return item;
} else {
throw new Error(`Invalid widget format: ${JSON.stringify(item)}`);
}
});
}
// Object with direct widget type properties
else {
const keys = Object.keys(selectedWidgets);
if (keys.length === 0) {
errors.push("❌ EMPTY OBJECT ERROR:\nselectedWidgets object is empty.\nObject should contain widget type information.\nCheck analyze_content_for_widgets output.");
return { valid: false, errors };
}
errors.push(`❌ UNRECOGNIZED OBJECT FORMAT ERROR:\nselectedWidgets object format not recognized.\nReceived keys: ${keys.join(', ')}\nExpected keys: 'data', 'selectedWidgetTypes', 'widgets'\nObject: ${JSON.stringify(selectedWidgets, null, 2)}`);
return { valid: false, errors };
}
}
// Handle string format (single widget)
else if (typeof selectedWidgets === 'string') {
widgetTypes = [selectedWidgets];
}
// Invalid format
else {
errors.push(`❌ INVALID TYPE ERROR:\nselectedWidgets must be an array, object, or string.\nReceived: ${typeof selectedWidgets}\nValue: ${JSON.stringify(selectedWidgets, null, 2)}`);
return { valid: false, errors };
}
} catch (extractionError) {
errors.push(`❌ EXTRACTION ERROR:\nFailed to extract widget types: ${extractionError.message}\nInput structure may be malformed or unsupported.\nCheck analyze_content_for_widgets output format.`);
return { valid: false, errors };
}
// Final validation
if (widgetTypes.length === 0) {
errors.push("❌ NO WIDGET TYPES EXTRACTED ERROR:\nNo valid widget types found after processing.\nInput may be malformed or empty.\nEnsure analyze_content_for_widgets generated valid recommendations.");
return { valid: false, errors };
}
// Remove duplicates and filter out empty/null values
const normalizedWidgetTypes = [...new Set(widgetTypes)].filter(type => type && typeof type === 'string' && type.trim().length > 0);
if (normalizedWidgetTypes.length === 0) {
errors.push("❌ NO VALID WIDGET TYPES ERROR:\nAll extracted widget types were empty, null, or invalid.\nExtracted types: " + JSON.stringify(widgetTypes));
return { valid: false, errors };
}
return { valid: true, normalizedWidgetTypes, errors: [] };
}
/**
* VALIDATE individual widget types against supported list
*/
validateIndividualWidgetTypes(widgetTypes) {
const errors = [];
const invalidTypes = [];
const unsupportedTypes = [];
widgetTypes.forEach(type => {
// Check format (should be like "head-1", "text-1", etc.)
if (!type.match(/^[a-z-]+(-\d+)?$/)) {
invalidTypes.push(type);
return;
}
// Check if supported
if (!this.supportedWidgetTypes.includes(type)) {
unsupportedTypes.push(type);
}
});
if (invalidTypes.length > 0) {
errors.push(`❌ INVALID WIDGET TYPE FORMAT ERROR:\nThese widget types have invalid format: ${invalidTypes.join(', ')}\nValid format examples: 'head-1', 'text-1', 'quiz-1'\nWidget types should follow 'name-number' pattern.`);
}
if (unsupportedTypes.length > 0) {
errors.push(`❌ UNSUPPORTED WIDGET TYPES ERROR:\nThese widget types are not supported: ${unsupportedTypes.join(', ')}\n\nSupported widget types:\n${this.supportedWidgetTypes.map(t => ` • ${t}`).join('\n')}\n\nCheck widget type spelling and ensure they're from the supported list.`);
}
if (errors.length > 0) {
return { valid: false, errors };
}
return { valid: true, errors: [] };
}
/**
* Get specific requirements for validated widget types
*/
getSpecificRequirements(widgetTypes) {
const requirements = {};
widgetTypes.forEach(widgetType => {
const requirement = this.requirementsDatabase[widgetType];
if (requirement) {
requirements[widgetType] = {
...requirement,
validated: true,
failFastNote: "All requirements are mandatory - missing fields will cause validation errors"
};
} else {
// This should not happen after validation, but fail fast if it does
throw new Error(`Requirements not found for validated widget type: ${widgetType}. This indicates a system error.`);
}
});
return requirements;
}
/**
* Generate targeted examples for validated widgets
*/
generateTargetedExamples(widgetTypes) {
const examples = {};
widgetTypes.forEach(widgetType => {
const baseType = widgetType.split('-')[0];
examples[widgetType] = this.getExampleForWidgetType(baseType, widgetType);
});
return examples;
}
/**
* Generate validation checklist for widgets
*/
generateValidationChecklist(widgetTypes) {
const rules = {
general: [
"All widgets must have unique IDs",
"Widget content must not be empty",
"All required fields must be present",
"Content must be educationally appropriate"
],
specific: {}
};
widgetTypes.forEach(widgetType => {
const baseType = widgetType.split('-')[0];
rules.specific[widgetType] = this.getValidationRulesForType(baseType);
});
return rules;
}
/**
* Generate structure guidance based on widgets
*/
generateStructureGuidance(widgetTypes) {
const hasAssessment = widgetTypes.some(type => type.startsWith('quiz-') || type.startsWith('flashcards-'));
const hasText = widgetTypes.some(type => type.startsWith('text-'));
const hasMedia = widgetTypes.some(type => type.startsWith('image-') || type.startsWith('video-'));
return {
requiredFormat: {
metadata: {
topic: "string - lesson topic/title",
gradeLevel: "string - target grade level",
subject: "string - subject area",
duration: "string - estimated duration"
},
widgets: "array - one object per widget with all required fields"
},
recommendations: {
structure: hasText ? "Include substantial text content" : "Consider adding explanatory content",
assessment: hasAssessment ? "Include complete assessment content" : "Consider adding assessment widgets",
media: hasMedia ? "Provide media descriptions or content" : "Consider visual enhancements"
},
failFastWarnings: [
"All widget fields are validated strictly",
"Missing required fields will cause immediate failure",
"Content validation is comprehensive - ensure quality",
"Assessment widgets require complete questions and answers"
]
};
}
/**
* Get example for specific widget type
*/
getExampleForWidgetType(baseType, fullType) {
const examples = {
'head': {
id: "widget_001",
type: fullType,
content: {
title: "Lesson Title",
subtitle: "Educational objective"
}
},
'text': {
id: "widget_002",
type: fullType,
content: {
text: "Educational content with detailed explanations...",
title: "Section Title"
}
},
'quiz': {
id: "widget_003",
type: fullType,
content: {
questions: [
{
question: "What is the main concept?",
options: ["Option A", "Option B", "Option C", "Option D"],
correct: 0
}
]
}
},
'flashcards': {
id: "widget_004",
type: fullType,
content: {
cards: [
{
front: "Term or Question",
back: "Definition or Answer"
}
]
}
},
'image': {
id: "widget_005",
type: fullType,
content: {
src: "image_url_or_description",
alt: "Alternative text description",
caption: "Image caption"
}
},
'list': {
id: "widget_006",
type: fullType,
content: {
items: ["Item 1", "Item 2", "Item 3"],
title: "List Title"
}
}
};
return examples[baseType] || {
id: "widget_xxx",
type: fullType,
content: "Content structure varies by widget type"
};
}
/**
* Get validation rules for widget type
*/
getValidationRulesForType(baseType) {
const rules = {
'head': ["Title must be present", "Content should be concise"],
'text': ["Text content must not be empty", "Should be educationally substantial"],
'quiz': ["Must have at least one question", "All questions need options and correct answers"],
'flashcards': ["Must have at least one card", "Front and back content required"],
'image': ["Image source or description required", "Alt text mandatory"],
'list': ["Must have at least one item", "Items should be meaningful"]
};
return rules[baseType] || ["Standard content validation applies"];
}
/**
* Get supported widget types
*/
getSupportedWidgetTypes() {
return [
'head-1', 'head-2',
'text-1', 'text-2',
'quiz-1', 'quiz-2',
'flashcards-1', 'flashcards-2',
'image-1', 'image-2',
'video-1', 'video-2',
'list-1', 'list-2',
'hotspots-1', 'hotspots-2'
];
}
/**
* Initialize requirements database
*/
initializeRequirementsDatabase() {
return {
'head-1': {
description: "Professional lesson header",
requiredFields: ["id", "type", "content"],
contentStructure: {
title: "string - main lesson title",
subtitle: "string (optional) - lesson subtitle or objective"
},
validation: ["title must not be empty", "id must be unique"]
},
'text-1': {
description: "Main explanatory content widget",
requiredFields: ["id", "type", "content"],
contentStructure: {
text: "string - main educational content",
title: "string (optional) - section title"
},
validation: ["text must not be empty", "content should be educational"]
},
'quiz-1': {
description: "Interactive quiz widget",
requiredFields: ["id", "type", "content"],
contentStructure: {
questions: "array - quiz questions with options and answers"
},
validation: ["must have at least one question", "questions need options and correct answers"]
},
'flashcards-1': {
description: "Vocabulary flashcards widget",
requiredFields: ["id", "type", "content"],
contentStructure: {
cards: "array - flashcard terms and definitions"
},
validation: ["must have at least one card", "cards need front and back content"]
},
'image-1': {
description: "Educational image widget",
requiredFields: ["id", "type", "content"],
contentStructure: {
src: "string - image URL or description",
alt: "string - alternative text",
caption: "string (optional) - image caption"
},
validation: ["image source required", "alt text mandatory"]
},
'list-1': {
description: "Structured list widget",
requiredFields: ["id", "type", "content"],
contentStructure: {
items: "array - list items",
title: "string (optional) - list title"
},
validation: ["must have at least one item", "items should be meaningful"]
}
};
}
}
/**
* Create and export the tool instance for JIT server integration
*/
export function createWidgetRequirementsToolV110() {
const requirementsTool = new WidgetRequirementsToolV110();
return {
name: 'get_widget_requirements_v110',
description: 'STEP 3 v1.1.0: Get precise API requirements for selected widgets with fail-fast validation. Provides comprehensive validation and detailed error reporting for development.',
inputSchema: {
type: 'object',
properties: {
selectedWidgets: {
type: 'object',
description: 'Selected widget types from analyze_content_for_widgets output (various formats supported)'
}
},
required: ['selectedWidgets']
},
handler: async (input) => {
return await requirementsTool.getWidgetRequirements(input);
}
};
}