UNPKG

@simonecoelhosfo/optimizely-mcp-server

Version:

Optimizely MCP Server for AI assistants with integrated CLI tools

1,343 lines (1,203 loc) 59.4 kB
#!/usr/bin/env node /** * OptimizelySDKParser.js * * Standalone parser for Optimizely SDK documentation that mimics * swagger-parser.js functionality for MCP server integration. * * This parser provides the same interface as swagger-parser but works * with our custom Optimizely SDK documentation JSON format. */ const fs = require('fs'); const path = require('path'); // JSONata is imported conditionally to avoid issues if not available let jsonata; try { jsonata = require('jsonata'); } catch (e) { // JSONata not available, will use basic search } class OptimizelySDKParser { constructor(documentationPath = null) { this.documentationPath = documentationPath; this.documentation = null; this.cache = new Map(); this._isLoaded = false; } /** * Load documentation from file or string */ loadDocumentation(source) { try { if (typeof source === 'string' && fs.existsSync(source)) { // Load from file const content = fs.readFileSync(source, 'utf8'); this.documentation = JSON.parse(content); this.documentationPath = source; } else if (typeof source === 'string') { // Parse as JSON string this.documentation = JSON.parse(source); } else if (typeof source === 'object') { // Use directly this.documentation = source; } else { throw new Error('Invalid source type'); } this._isLoaded = true; this.cache.clear(); // Clear cache when documentation changes return this.documentation; } catch (error) { throw new Error(`Failed to load documentation: ${error.message}`); } } /** * Mirror swagger-parser's getAPISummary method * Returns high-level overview of SDK documentation */ getAPISummary() { this._ensureLoaded(); const summary = { title: 'Optimizely SDK Reference', version: this.documentation.optimizely_sdk_reference?.version || 'Unknown', description: this.documentation.optimizely_sdk_reference?.description || '', generated: this.documentation.optimizely_sdk_reference?.generated || '', sdks: this._getAllSDKs(), concepts: this._getSharedConcepts(), totalMethods: this._countTotalMethods(), lastModified: this._getLastModified() }; return summary; } /** * Mirror swagger-parser's getAllEndpoints method * Returns all SDK methods across all platforms */ getAllMethods() { this._ensureLoaded(); const cacheKey = 'all_methods'; if (this.cache.has(cacheKey)) { return this.cache.get(cacheKey); } const methods = []; const sdks = this._getAllSDKs(); sdks.forEach(sdk => { const sdkMethods = this._getSDKMethods(sdk); sdkMethods.forEach(method => { methods.push({ sdk: sdk, method: method.name, signature: method.signature, description: method.description, platform: method.platform, category: method.category, since: method.since, deprecated: method.deprecated || false }); }); }); this.cache.set(cacheKey, methods); return methods; } /** * Mirror swagger-parser's getEndpointDetails method * Returns detailed information about a specific SDK method */ getMethodDetails(sdk, methodName) { this._ensureLoaded(); const cacheKey = `method_${sdk}_${methodName}`; if (this.cache.has(cacheKey)) { return this.cache.get(cacheKey); } const methodDetails = this._findMethodInSDK(sdk, methodName); if (!methodDetails) { return null; } const details = { sdk: sdk, method: methodName, signature: methodDetails.signature, description: methodDetails.description, parameters: methodDetails.params || methodDetails.parameters, returns: methodDetails.returns, examples: methodDetails.examples || methodDetails.example, platform_specific: methodDetails.platform_specific, notes: methodDetails.notes, since: methodDetails.since, deprecated: methodDetails.deprecated, related_methods: this._findRelatedMethods(sdk, methodName), see_also: methodDetails.see_also }; this.cache.set(cacheKey, details); return details; } /** * Mirror swagger-parser's findProperty method * Searches for concepts, methods, or configuration across documentation */ findProperty(query, options = {}) { this._ensureLoaded(); // Use enhanced search if available and JSONata is loaded if (options.useEnhancedSearch !== false && jsonata) { return this.findPropertyEnhanced(query, options); } const results = []; const searchTerms = query.toLowerCase().split(/\s+/); // Search in shared concepts const concepts = this._getSharedConcepts(); concepts.forEach(concept => { if (this._matchesSearch(concept, searchTerms)) { results.push({ type: 'concept', name: concept.name, description: concept.description, location: 'shared_concepts', relevance: this._calculateRelevance(concept, searchTerms) }); } }); // Search in SDK methods const allMethods = this.getAllMethods(); allMethods.forEach(method => { if (this._matchesSearch(method, searchTerms)) { results.push({ type: 'method', sdk: method.sdk, name: method.method, description: method.description, signature: method.signature, location: `${method.sdk}.api.${method.method}`, relevance: this._calculateRelevance(method, searchTerms) }); } }); // Special handling for Optimizely Agent queries if (searchTerms.some(term => ['agent', 'microservice', 'docker', 'kubernetes', 'k8s', 'container', 'http', 'api', 'webhook', 'admin', 'metrics', 'prometheus', 'datafile'].includes(term))) { // Add specific Agent-related results const agentInfo = this.getSDKInfo('optimizely_agent'); if (agentInfo && agentInfo.description) { results.push({ type: 'platform', name: 'optimizely_agent', description: agentInfo.description, signature: 'HTTP API Microservice', location: 'optimizely_agent', relevance: this._calculateRelevance({ name: 'optimizely_agent', description: agentInfo.description }, searchTerms) + 20 }); } // Add Agent endpoints as results if (agentInfo && agentInfo.api_endpoints) { Object.entries(agentInfo.api_endpoints).forEach(([endpointName, endpointData]) => { if (this._matchesSearch({ name: endpointName, ...endpointData }, searchTerms)) { results.push({ type: 'endpoint', name: endpointName, description: endpointData.description || `${endpointName} endpoint`, signature: `${endpointData.method || 'GET'} ${endpointData.path}`, location: `optimizely_agent.api_endpoints.${endpointName}`, relevance: this._calculateRelevance({ name: endpointName, ...endpointData }, searchTerms) + 15 }); } }); } } // Special handling for Web Experimentation queries if (searchTerms.some(term => ['web', 'javascript', 'browser', 'window', 'optimizely', 'get', 'push', 'state', 'visitor', 'utils', 'dcp', 'behavior', 'element', 'wait', 'observe', 'poll'].includes(term))) { // Add specific Web Experimentation suggestions const webExpMethods = [ { name: 'window.optimizely.get', description: 'Retrieve data from Optimizely (state, visitor, data, etc.)' }, { name: 'window.optimizely.push', description: 'Execute commands (event, page, user, etc.)' }, { name: 'push:event', description: 'Track custom events' }, { name: 'push:page', description: 'Activate/deactivate pages' }, { name: 'state.getActiveCampaigns', description: 'Get currently active campaigns' }, { name: 'state.getCampaignStates', description: 'Get campaign states with filtering' }, { name: 'utils.waitForElement', description: 'Returns a Promise resolved with matching DOM element' }, { name: 'utils.observeSelector', description: 'Observes DOM for elements matching selector' }, { name: 'utils.poll', description: 'Polls function until truthy or timeout' }, { name: 'utils.waitUntil', description: 'Waits until condition function returns true' }, { name: 'dcp.getAttributeValue', description: 'Gets external attribute value from DCP' }, { name: 'dcp.waitForAttributeValue', description: 'Waits for external attribute from DCP' }, { name: 'behavior.query', description: 'Queries visitor events based on criteria' } ]; webExpMethods.forEach(method => { if (this._matchesSearch(method, searchTerms)) { results.push({ type: 'method', sdk: 'web_experimentation', name: method.name, description: method.description, signature: method.name + '()', location: 'web_experimentation.api', relevance: this._calculateRelevance(method, searchTerms) + 10 // Boost Web Exp results }); } }); } // Sort by relevance results.sort((a, b) => b.relevance - a.relevance); // Apply options if (options.limit) { results.splice(options.limit); } return results; } /** * Enhanced search using JSONata for intelligent query matching * Supports natural language queries and smart fuzzy matching */ findPropertyEnhanced(query, options = {}) { this._ensureLoaded(); const searchQuery = query.toLowerCase(); const results = []; // Create search patterns for different query types const searchPatterns = this._createSearchPatterns(searchQuery); // Build JSONata expressions for different search strategies const jsonataQueries = { // Direct method name match directMethod: ` $.optimizely_sdk_reference.[ feature_experimentation_sdks.*, web_experimentation_api, optimizely_agent ].api.*[ $lowercase($.name) in [${searchPatterns.exactTerms}] or $contains($lowercase($.name), "${searchPatterns.primaryTerm}") ]`, // Search in descriptions descriptionSearch: ` $.optimizely_sdk_reference.**[ description and (${searchPatterns.terms.map(term => `$contains($lowercase(description), "${term}")`).join(' or ')}) ]`, // Web Experimentation specific searches - build manually for state methods webExpStateSearch: null, // Will be built differently // Concept search conceptSearch: ` $.optimizely_sdk_reference.shared_concepts.*[ ${searchPatterns.terms.map(term => ` $contains($lowercase(name), "${term}") or $contains($lowercase(description), "${term}") `).join(' or ')} ]` }; // Execute searches with JSONata try { // Search for methods if (searchPatterns.isMethodSearch || searchPatterns.includesApiTerms) { this._executeJsonataSearch(jsonataQueries.directMethod, 'method', results); // Handle Web Experimentation state methods manually const stateMethods = this._getNestedValue(this.documentation, 'optimizely_sdk_reference.web_experimentation_api.api.get_api.types.state.methods'); if (stateMethods) { Object.entries(stateMethods).forEach(([methodName, methodData]) => { const lowerName = methodName.toLowerCase(); if (searchPatterns.terms.some(term => lowerName.includes(term))) { results.push({ type: 'method', data: { name: `state.${methodName}`, signature: methodData.signature, description: methodData.description || this._getStateMethodDescription(methodName), returns: methodData.returns, params: methodData.params } }); } }); } // Handle utils methods manually const utilsMethods = this._getNestedValue(this.documentation, 'optimizely_sdk_reference.web_experimentation_api.api.get_api.types.utils.methods'); if (utilsMethods) { Object.entries(utilsMethods).forEach(([methodName, methodData]) => { const lowerName = methodName.toLowerCase(); // Match if method name contains term OR search term is "utils" if (searchPatterns.terms.some(term => lowerName.includes(term) || term === 'utils')) { results.push({ type: 'method', data: { name: `utils.${methodName}`, signature: methodData.signature, description: methodData.description, returns: methodData.returns, params: methodData.params } }); } }); } // Handle dcp methods manually const dcpMethods = this._getNestedValue(this.documentation, 'optimizely_sdk_reference.web_experimentation_api.api.get_api.types.dcp.methods'); if (dcpMethods) { Object.entries(dcpMethods).forEach(([methodName, methodData]) => { const lowerName = methodName.toLowerCase(); if (searchPatterns.terms.some(term => lowerName.includes(term) || term === 'dcp')) { results.push({ type: 'method', data: { name: `dcp.${methodName}`, signature: methodData.signature, description: methodData.description, returns: methodData.returns, params: methodData.params } }); } }); } // Handle behavior methods manually const behaviorMethods = this._getNestedValue(this.documentation, 'optimizely_sdk_reference.web_experimentation_api.api.get_api.types.behavior.methods'); if (behaviorMethods) { Object.entries(behaviorMethods).forEach(([methodName, methodData]) => { const lowerName = methodName.toLowerCase(); if (searchPatterns.terms.some(term => lowerName.includes(term) || term === 'behavior')) { results.push({ type: 'method', data: { name: `behavior.${methodName}`, signature: methodData.signature, description: methodData.description, returns: methodData.returns, params: methodData.params } }); } }); } // Handle session methods manually const sessionMethods = this._getNestedValue(this.documentation, 'optimizely_sdk_reference.web_experimentation_api.api.get_api.types.session.methods'); if (sessionMethods) { Object.entries(sessionMethods).forEach(([methodName, methodData]) => { const lowerName = methodName.toLowerCase(); if (searchPatterns.terms.some(term => lowerName.includes(term) || term === 'session')) { results.push({ type: 'method', data: { name: `session.${methodName}`, signature: methodData.signature, description: methodData.description, returns: methodData.returns, params: methodData.params } }); } }); } // Handle push commands manually const pushCommands = this._getNestedValue(this.documentation, 'optimizely_sdk_reference.web_experimentation_api.api.push_api.commands'); if (pushCommands) { Object.entries(pushCommands).forEach(([commandName, commandData]) => { const lowerName = commandName.toLowerCase(); if (searchPatterns.terms.some(term => lowerName.includes(term))) { results.push({ type: 'method', data: { name: `push:${commandName}`, signature: commandData.signature, description: commandData.description } }); } }); } } // Search in shared concepts const concepts = this._getSharedConcepts(); concepts.forEach(concept => { const conceptName = concept.name ? concept.name.toLowerCase() : ''; const conceptDesc = concept.description ? concept.description.toLowerCase() : ''; if (searchPatterns.terms.some(term => conceptName.includes(term) || conceptDesc.includes(term) || concept.name === term )) { results.push({ type: 'concept', data: { name: concept.name, description: concept.description, concept_type: 'shared' } }); } }); // Search in descriptions this._executeJsonataSearch(jsonataQueries.descriptionSearch, 'description', results); // Search for concepts this._executeJsonataSearch(jsonataQueries.conceptSearch, 'concept', results); // Search in SDK initialization sections this._searchSDKInitialization(searchPatterns, results); // Special handling for common queries this._handleSpecialQueries(searchQuery, results); } catch (error) { console.error('JSONata search error:', error); // Fallback to basic search return this.findProperty(query, { ...options, useEnhancedSearch: false }); } // Deduplicate and score results const uniqueResults = this._deduplicateResults(results); // Calculate relevance scores uniqueResults.forEach(result => { result.relevance = this._calculateEnhancedRelevance(result, searchPatterns); }); // Sort by relevance uniqueResults.sort((a, b) => b.relevance - a.relevance); // Apply limit if (options.limit) { uniqueResults.splice(options.limit); } return uniqueResults; } /** * Create search patterns from query */ _createSearchPatterns(query) { const terms = query.split(/\s+/).filter(t => t.length > 0); const primaryTerm = terms[0] || ''; // Detect query intent const isMethodSearch = /\b(method|function|api|call)\b/i.test(query); const includesApiTerms = /\b(get|push|event|page|state|visitor|window|optimizely|utils|dcp|behavior|session|element|wait|observe|poll|waitforelement|observeselector|waituntil|getattributevalue|waitforattributevalue|query|dispatcher|batching|config|client|logger|datafile|manager|decision|agent|microservice|docker|kubernetes|webhook|metrics|prometheus|activate|decide|track|override|container|deployment|http|api)\b/i.test(query); const isWebExp = /\b(web|experimentation|javascript|browser|window)\b/i.test(query); // Create variations for fuzzy matching const termVariations = terms.flatMap(term => { const variations = [term]; // Add common variations if (term === 'get') variations.push('retrieve', 'fetch', 'read'); if (term === 'push') variations.push('send', 'track', 'dispatch'); if (term === 'event') variations.push('track', 'conversion', 'metric'); if (term === 'user') variations.push('visitor', 'context'); if (term === 'experiment') variations.push('test', 'variation'); return variations; }); return { terms, termVariations, primaryTerm, exactTerms: terms.map(t => `"${t}"`).join(','), isMethodSearch, includesApiTerms, isWebExp }; } /** * Execute JSONata query and collect results */ _executeJsonataSearch(queryStr, type, results) { try { const expression = jsonata(queryStr); const matches = expression.evaluate(this.documentation); if (matches) { const items = Array.isArray(matches) ? matches : [matches]; items.forEach(match => { if (match && typeof match === 'object') { results.push({ type, data: match, queryType: type }); } }); } } catch (error) { // Silently handle JSONata errors for individual queries } } /** * Handle special query patterns */ _handleSpecialQueries(query, results) { // Common question patterns const patterns = { 'how to track': ['push:event', 'trackEvent'], 'get active': ['state.getActiveCampaigns', 'state.getCampaignStates'], 'visitor id': ['window.optimizely.get:visitor_id', 'visitor.visitorId'], 'activate page': ['push:page'], 'user context': ['createUserContext', 'push:user'], 'make decision': ['decide', 'push:decision'], 'variation': ['getVariation', 'state.getVariationMap'], 'audience': ['audience', 'user.setAttributes'] }; Object.entries(patterns).forEach(([pattern, suggestions]) => { if (query.includes(pattern)) { suggestions.forEach(suggestion => { results.push({ type: 'suggestion', name: suggestion, description: `Suggested based on query: "${pattern}"`, relevance: 100 }); }); } }); } /** * Deduplicate results */ _deduplicateResults(results) { const seen = new Set(); const unique = []; results.forEach(result => { const key = `${result.type}:${result.name || result.data?.name || JSON.stringify(result.data).substring(0, 50)}`; if (!seen.has(key)) { seen.add(key); // Transform result into standard format if (result.type === 'method' && result.data) { const formatted = this._formatMethodResult(result.data); if (formatted) unique.push(formatted); } else if (result.type === 'concept' && result.data) { unique.push({ type: 'concept', name: result.data.name, description: result.data.description, location: 'shared_concepts' }); } else if (result.type === 'suggestion') { unique.push(result); } else if (result.type === 'initialization' && result.data) { unique.push({ type: 'initialization', name: result.data.name, sdk: result.data.sdk, signature: result.data.signature, description: result.data.description, config_options: result.data.config_options, location: `${result.data.sdk}.initialization` }); } else if (result.type === 'configuration' && result.data) { unique.push({ type: 'configuration', name: result.data.name, sdk: result.data.sdk, signature: result.data.signature, description: result.data.description, options: result.data.options, example: result.data.example, location: `${result.data.sdk}.configuration` }); } else if (result.type === 'installation' && result.data) { unique.push({ type: 'installation', name: result.data.name, sdk: result.data.sdk, description: result.data.description, installation: result.data.installation, location: `${result.data.sdk}.installation` }); } else if (result.type === 'initialization_example' && result.data) { unique.push({ type: 'initialization_example', name: result.data.name, sdk: result.data.sdk, platform: result.data.platform, description: result.data.description, example: result.data.example, location: `${result.data.sdk}.initialization.${result.data.platform}` }); } else if (result.data && result.data.name && result.data.name !== 'undefined') { // Only include results with valid names unique.push({ type: result.type, name: result.data.name, description: result.data.description || '', data: result.data }); } // Skip results without proper data or with name 'Unknown' or 'undefined' } }); // Filter out any remaining invalid results return unique.filter(item => item.name && item.name !== 'Unknown' && item.name !== 'undefined' && item.description !== undefined // Allow empty string but not undefined ); } /** * Format method search results */ _formatMethodResult(data) { // Handle different method data structures if (data.signature && data.name && data.name !== 'Unknown') { return { type: 'method', name: data.name, signature: data.signature, description: data.description || '', sdk: data.sdk || 'web_experimentation', returns: data.returns, params: data.params }; } return null; } /** * Calculate enhanced relevance score */ _calculateEnhancedRelevance(result, patterns) { let score = 0; // Boost for exact matches patterns.terms.forEach(term => { if (result.name && result.name.toLowerCase().includes(term)) { score += 50; } if (result.description && result.description.toLowerCase().includes(term)) { score += 20; } }); // Boost for suggestions if (result.type === 'suggestion') { score += result.relevance || 80; } // Boost for primary term matches if (result.name && result.name.toLowerCase().includes(patterns.primaryTerm)) { score += 30; } // Context-based scoring if (patterns.isWebExp && result.sdk === 'web_experimentation') { score += 25; } return score; } /** * Search in SDK initialization sections */ _searchSDKInitialization(searchPatterns, results) { const sdks = this._getAllSDKs(); sdks.forEach(sdk => { const sdkPath = this._getSDKPath(sdk); const sdkData = this._getNestedValue(this.documentation, sdkPath); if (!sdkData) return; // Search in initialization section if (sdkData.initialization) { const initSection = sdkData.initialization; // Check factory functions if (initSection.factory_functions) { Object.entries(initSection.factory_functions).forEach(([funcName, funcData]) => { const lowerName = funcName.toLowerCase(); const lowerDesc = (funcData.description || '').toLowerCase(); const configOptionsText = JSON.stringify(funcData.config_options || {}).toLowerCase(); if (searchPatterns.terms.some(term => lowerName.includes(term) || lowerDesc.includes(term) || configOptionsText.includes(term) || (term === 'initialization' || term === 'configuration' || term === 'setup') )) { results.push({ type: 'initialization', data: { name: funcName, sdk: sdk, signature: funcData.signature, description: funcData.description || `Factory function for creating ${sdk} client`, config_options: funcData.config_options, category: 'initialization' } }); } }); } // Check project config managers if (initSection.project_config_managers) { Object.entries(initSection.project_config_managers).forEach(([managerName, managerData]) => { const lowerName = managerName.toLowerCase(); const lowerFunc = (managerData.function || '').toLowerCase(); const lowerDesc = (managerData.description || managerData.usage || '').toLowerCase(); if (searchPatterns.terms.some(term => lowerName.includes(term) || lowerFunc.includes(term) || lowerDesc.includes(term) || term === 'manager' || term === 'configuration' )) { results.push({ type: 'configuration', data: { name: managerData.function || managerName, sdk: sdk, signature: managerData.signature, description: managerData.description || managerData.usage || `Project config manager: ${managerName}`, options: managerData.options, example: managerData.example, category: 'configuration' } }); } }); } // Check event processors if (initSection.event_processors) { Object.entries(initSection.event_processors).forEach(([processorName, processorData]) => { const lowerName = processorName.toLowerCase(); const lowerFunc = (processorData.function || '').toLowerCase(); const lowerDesc = (processorData.description || '').toLowerCase(); if (searchPatterns.terms.some(term => lowerName.includes(term) || lowerFunc.includes(term) || lowerDesc.includes(term) || term === 'processor' || term === 'event' )) { results.push({ type: 'configuration', data: { name: processorData.function || processorName, sdk: sdk, signature: processorData.signature, description: processorData.description || `Event processor: ${processorName}`, options: processorData.options, example: processorData.example, category: 'configuration' } }); } }); } // Check logger configurations if (initSection.logger_configurations) { Object.entries(initSection.logger_configurations).forEach(([loggerName, loggerData]) => { const lowerName = loggerName.toLowerCase(); const lowerDesc = (loggerData.description || '').toLowerCase(); if (searchPatterns.terms.some(term => lowerName.includes(term) || lowerDesc.includes(term) || term === 'logger' || term === 'logging' )) { results.push({ type: 'configuration', data: { name: loggerName, sdk: sdk, description: loggerData.description || `Logger configuration: ${loggerName}`, usage: loggerData.usage, example: loggerData.example, category: 'configuration' } }); } }); } // Check complete initialization examples if (initSection.complete_initialization) { const hasRelevantTerms = searchPatterns.terms.some(term => ['initialization', 'init', 'setup', 'complete', 'example', 'browser', 'node', 'configuration'].includes(term) ); if (hasRelevantTerms) { Object.entries(initSection.complete_initialization).forEach(([platform, example]) => { results.push({ type: 'initialization_example', data: { name: `Complete ${platform} initialization example`, sdk: sdk, platform: platform, description: `Complete initialization example for ${sdk} on ${platform}`, example: example, category: 'initialization' } }); }); } } } // Also check installation section if searching for setup/installation terms if (sdkData.installation && searchPatterns.terms.some(term => ['install', 'installation', 'setup', 'npm', 'yarn', 'cdn'].includes(term) )) { results.push({ type: 'installation', data: { name: `${sdk} Installation`, sdk: sdk, description: `Installation instructions for ${sdk}`, installation: sdkData.installation, category: 'setup' } }); } }); } /** * Get SDK-specific information */ getSDKInfo(sdkName) { this._ensureLoaded(); const cacheKey = `sdk_info_${sdkName}`; if (this.cache.has(cacheKey)) { return this.cache.get(cacheKey); } const sdkPath = this._getSDKPath(sdkName); if (!sdkPath) { return null; } const sdkData = this._getNestedValue(this.documentation, sdkPath); if (!sdkData) { return null; } const info = { name: sdkName, package: sdkData.package, version: sdkData.current_version || sdkData.version, installation: sdkData.installation, platform: this._detectPlatform(sdkName), initialization: sdkData.initialization, api_methods: this._getSDKMethods(sdkName), platform_specific: sdkData[`${sdkName}_specific`] || sdkData.platform_specific, examples: sdkData.examples, min_version: sdkData.min_version || sdkData.min_platform_version, // Additional fields for Optimizely Agent description: sdkData.description, architecture: sdkData.architecture, deployment: sdkData.deployment, configuration: sdkData.configuration, api_endpoints: sdkData.api_endpoints, admin_endpoints: sdkData.admin_endpoints, webhook_endpoints: sdkData.webhook_endpoints }; this.cache.set(cacheKey, info); return info; } /** * Get concept details (mirrors finding schema/model definitions) */ getConceptDetails(conceptName) { this._ensureLoaded(); const cacheKey = `concept_${conceptName}`; if (this.cache.has(cacheKey)) { return this.cache.get(cacheKey); } const concept = this._findConcept(conceptName); if (!concept) { return null; } this.cache.set(cacheKey, concept); return concept; } /** * Get usage examples for a specific method */ getMethodExamples(sdk, methodName) { const methodDetails = this.getMethodDetails(sdk, methodName); if (!methodDetails || !methodDetails.examples) { return []; } // Handle different example formats if (Array.isArray(methodDetails.examples)) { return methodDetails.examples; } else if (typeof methodDetails.examples === 'object') { // Convert object-based examples to array format return Object.entries(methodDetails.examples).map(([title, code]) => ({ title: title.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase()), code: code, language: this._detectLanguage(sdk) })); } else if (typeof methodDetails.examples === 'string') { return [{ title: 'Example', code: methodDetails.examples, language: this._detectLanguage(sdk) }]; } return []; } _detectLanguage(sdk) { const languageMap = { 'javascript': 'javascript', 'react': 'javascript', 'android': 'java', 'java': 'java', 'csharp': 'csharp', 'go': 'go', 'ruby': 'ruby', 'swift': 'swift', 'python': 'python', 'php': 'php', 'web_experimentation': 'javascript', 'optimizely_agent': 'json' }; return languageMap[sdk] || 'javascript'; } // Private helper methods _ensureLoaded() { if (!this._isLoaded || !this.documentation) { throw new Error('Documentation not loaded. Call loadDocumentation() first.'); } } _getAllSDKs() { const sdks = []; // Feature Experimentation SDKs if (this.documentation.optimizely_sdk_reference?.feature_experimentation_sdks) { Object.keys(this.documentation.optimizely_sdk_reference.feature_experimentation_sdks) .forEach(sdk => sdks.push(sdk)); } // Web Experimentation API if (this.documentation.optimizely_sdk_reference?.web_experimentation_api) { sdks.push('web_experimentation'); } // Optimizely Agent if (this.documentation.optimizely_sdk_reference?.optimizely_agent) { sdks.push('optimizely_agent'); } return sdks; } _getSharedConcepts() { const concepts = []; const sharedConcepts = this.documentation.optimizely_sdk_reference?.shared_concepts; if (sharedConcepts) { Object.entries(sharedConcepts).forEach(([name, concept]) => { concepts.push({ name, description: concept.description, configuration: concept.configuration, examples: concept.implementation_examples || concept.examples }); }); } return concepts; } _getSDKMethods(sdkName) { const sdkPath = this._getSDKPath(sdkName); if (!sdkPath) return []; const sdkData = this._getNestedValue(this.documentation, sdkPath); if (!sdkData) return []; const methods = []; // Handle Web Experimentation API's different structure if (sdkName === 'web_experimentation') { // Add window.optimizely methods if (sdkData.api && sdkData.api['window.optimizely']) { const windowApi = sdkData.api['window.optimizely']; if (windowApi.methods) { windowApi.methods.forEach(methodName => { methods.push({ name: `window.optimizely.${methodName}`, signature: `window.optimizely.${methodName}()`, description: `${methodName} method on window.optimizely object`, platform: sdkName, category: this._categorizeMethod(methodName), since: null, deprecated: false }); }); } } // Add push API commands as methods if (sdkData.api && sdkData.api.push_api && sdkData.api.push_api.commands) { Object.entries(sdkData.api.push_api.commands).forEach(([commandName, commandData]) => { methods.push({ name: `push:${commandName}`, signature: commandData.signature || `{ type: '${commandName}', ... }`, description: commandData.description || `Push command: ${commandName}`, platform: sdkName, category: this._categorizeMethod(commandName), since: commandData.since, deprecated: commandData.deprecated || false }); }); } // CRITICAL: Add get_api and its nested state methods if (sdkData.api && sdkData.api.get_api) { const getApi = sdkData.api.get_api; // Add main get method methods.push({ name: 'window.optimizely.get', signature: getApi.signature, description: getApi.description, platform: sdkName, category: 'retrieval', since: null, deprecated: false }); // Add all get types and their methods if (getApi.types) { Object.entries(getApi.types).forEach(([typeName, typeData]) => { // Add the get type itself methods.push({ name: `window.optimizely.get('${typeName}')`, signature: typeData.signature || `window.optimizely.get('${typeName}')`, description: typeData.description || `Get ${typeName} data`, platform: sdkName, category: 'retrieval', since: null, deprecated: false }); // Add methods for objects that have method collections if (typeData.methods) { Object.entries(typeData.methods).forEach(([methodName, methodData]) => { let fullMethodName; let methodDescription; if (typeName === 'state') { fullMethodName = `state.${methodName}`; methodDescription = methodData.description || `State method: ${methodName}`; } else if (typeName === 'utils') { fullMethodName = `utils.${methodName}`; methodDescription = methodData.description || `Utility function: ${methodName}`; } else if (typeName === 'dcp') { fullMethodName = `dcp.${methodName}`; methodDescription = methodData.description || `DCP method: ${methodName}`; } else if (typeName === 'behavior') { fullMethodName = `behavior.${methodName}`; methodDescription = methodData.description || `Behavior method: ${methodName}`; } else if (typeName === 'session') { fullMethodName = `session.${methodName}`; methodDescription = methodData.description || `Session method: ${methodName}`; } else { fullMethodName = `${typeName}.${methodName}`; methodDescription = methodData.description || `${typeName} method: ${methodName}`; } methods.push({ name: fullMethodName, signature: methodData.signature, description: methodDescription, platform: sdkName, category: this._categorizeMethod(methodName), returns: methodData.returns, example: methodData.example, params: methodData.params, since: null, deprecated: false }); }); } }); } } // Add other API methods if (sdkData.api) { Object.entries(sdkData.api).forEach(([methodName, methodData]) => { if (methodName !== 'window.optimizely' && methodName !== 'push_api' && methodName !== 'get_api' && typeof methodData === 'object' && methodData.signature) { methods.push({ name: methodName, signature: methodData.signature, description: methodData.description, platform: sdkName, category: this._categorizeMethod(methodName), since: methodData.since, deprecated: methodData.deprecated || false }); } }); } } else { // Standard SDK structure if (sdkData.api) { Object.entries(sdkData.api).forEach(([methodName, methodData]) => { methods.push({ name: methodName, signature: methodData.signature, description: methodData.description, platform: sdkName, category: this._categorizeMethod(methodName), since: methodData.since, deprecated: methodData.deprecated }); }); } } return methods; } _getSDKPath(sdkName) { if (sdkName === 'web_experimentation') { return 'optimizely_sdk_reference.web_experimentation_api'; } if (sdkName === 'optimizely_agent') { return 'optimizely_sdk_reference.optimizely_agent'; } return `optimizely_sdk_reference.feature_experimentation_sdks.${sdkName}`; } _findMethodInSDK(sdk, methodName) { const sdkPath = this._getSDKPath(sdk); if (!sdkPath) return null; // Handle Web Experimentation API's different structure if (sdk === 'web_experimentation') { const sdkData = this._getNestedValue(this.documentation, sdkPath); if (!sdkData || !sdkData.api) return null; // Check if it's a window.optimizely method if (methodName.startsWith('window.optimizely.')) { const windowApi = sdkData.api['window.optimizely']; if (windowApi && windowApi.methods && windowApi.methods.includes(methodName.replace('window.optimizely.', ''))) { return { signature: `${methodName}()`, description: `${methodName.replace('window.optimizely.', '')} method on window.optimizely object`, examples: windowApi.examples }; } // Special handling for window.optimizely.get if (methodName === 'window.optimizely.get' && sdkData.api.get_api) { const getApi = sdkData.api.get_api; // Collect examples from all types const examples = []; if (getApi.types) { Object.entries(getApi.types).forEach(([typeName, typeData]) => { if (typeData.example) { examples.push({ title: `Getting ${typeName} data`, code: typeData.example, language: 'javascript' }); } }); } return { ...getApi, examples: examples.length > 0 ? examples : undefined }; } // Check if it's a get type like window.optimizely.get('state') const getMatch = methodName.match(/window\.optimizely\.get\('(.+)'\)/); if (getMatch && sdkData.api.get_api && sdkData.api.get_api.types) { const typeName = getMatch[1]; if (sdkData.api.get_api.types[typeName]) { return sdkData.api.get_api.types[typeName]; } } } // Check if it's a state method if (methodName.startsWith('state.')) { const stateMethodName = methodName.replace('state.', ''); if (sdkData.api.get_api && sdkData.api.get_api.types && sdkData.api.get_api.types.state && sdkData.api.get_api.types.state.methods && sdkData.api.get_api.types.state.methods[stateMethodName]) { const methodData = sdkData.api.get_api.types.state.methods[stateMethodName]; // Add a description if it doesn't exist return { ...methodData, description: methodData.description || this._getStateMethodDescription(stateMethodName) }; } } // Check if it's a utils method if (methodName.startsWith('utils.')) { const utilsMethodName = methodName.replace('utils.', ''); if (sdkData.api.get_api && sdkData.api.get_api.types && sdkData.api.get_api.types.utils && sdkData.api.get_api.types.utils.methods && sdkData.api.get_api.types.utils.methods[utilsMethodName]) { const methodData = sdkData.api.get_api.types.utils.methods[utilsMethodName]; return { ...methodData, description: methodData.description || `Utility method: ${utilsMethodName}` }; } } // Check if it's a dcp method if (methodName.startsWith('dcp.')) { const dcpMethodName = methodName.replace('dcp.', ''); if (sdkData.api.get_api && sdkData.api.get_api.types && sdkData.api.get_api.types.dcp && sdkData.api.get_api.types.dcp.methods && sdkData.api.get_api.types.dcp.methods[dcpMethodName]) { const methodData = sdkData.api.get_api.types.dcp.methods[dcpMethodName]; return { ...methodData, description: methodData.description || `DCP method: ${dcpMethodName}` }; } } // Check if it's a behavior method if (methodName.startsWith('behavior.')) { const behaviorMethodName = methodName.replace('behavior.', ''); if (sdkData.api.get_api && sdkData.api.get_api.types && sdkData.api.get_api.types.behavior && sdkData.api.get_api.types.behavior.methods && sdkData.api.get_api.types.behavior.methods[behaviorMethodName]) { const methodData = sdkData.api.get_api.types.behavior.methods[behaviorMethodName]; return { ...method