@simonecoelhosfo/optimizely-mcp-server
Version:
Optimizely MCP Server for AI assistants with integrated CLI tools
1,343 lines (1,203 loc) • 59.4 kB
JavaScript
#!/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