@dollhousemcp/mcp-server
Version:
DollhouseMCP - A Model Context Protocol (MCP) server that enables dynamic AI persona management from markdown files, allowing Claude and other compatible AI assistants to activate and switch between different behavioral personas.
133 lines • 16.9 kB
JavaScript
/**
* AggregationService - Server-side count and group_by aggregation for elements
*
* Provides lightweight aggregation queries that return counts and groupings
* without fetching full paginated item lists. This saves ~98% of tokens
* compared to listing all items when only counts are needed.
*
* Issue #309: Token-efficient aggregation
*
* DESIGN:
* - Uses FilterService.filter() directly (not full query pipeline) for efficiency
* - Deduplicates array values in group_by to prevent double-counting
* - Validates group_by fields against a whitelist to prevent internal field exposure
* - Stateless and safe for concurrent use
*
* @module services/query/AggregationService
*/
import { getMetadataField } from './types.js';
import { FilterService } from './FilterService.js';
import { logger } from '../../utils/logger.js';
/**
* Metadata fields allowed for group_by aggregation.
*
* Only these fields can be used with group_by to prevent:
* - Exposure of internal/sensitive metadata fields
* - Arbitrary property enumeration attacks
* - Grouping on fields that produce meaningless results (e.g., timestamps)
*/
const ALLOWED_GROUP_BY_FIELDS = new Set([
'category',
'author',
'tags',
'status',
'version',
]);
/**
* Aggregate elements with optional filtering and grouping.
*
* Uses FilterService.filter() directly for efficiency — skips the sort and
* paginate steps that the full query pipeline would apply, since aggregation
* only needs to count elements, not order or slice them.
*
* @param elements - Full element array to aggregate over
* @param elementType - Element type string for the result
* @param options - Aggregation options (count, group_by)
* @param filters - Optional filter criteria to apply before aggregating
* @returns Aggregation result with count and optional groups
*
* @example
* ```typescript
* // Count all personas
* const result = aggregateElements(personas, 'persona', { count: true });
* // → { count: 42, element_type: 'persona' }
*
* // Count personas grouped by category
* const grouped = aggregateElements(personas, 'persona',
* { count: true, group_by: 'category' });
* // → { count: 42, element_type: 'persona', groups: { assistant: 15, creative: 27 } }
*
* // Count with filters
* const filtered = aggregateElements(personas, 'persona',
* { count: true, group_by: 'tags' },
* { status: 'active' });
* // → { count: 10, element_type: 'persona', groups: { typescript: 5, python: 3, rust: 2 } }
* ```
*/
export function aggregateElements(elements, elementType, options, filters) {
// Apply filters directly via FilterService (no sort/paginate needed)
const filterService = new FilterService();
const filtered = filters ? filterService.filter(elements, filters) : elements;
logger.debug('AggregationService.aggregate', {
inputCount: elements.length,
filteredCount: filtered.length,
hasGroupBy: !!options.group_by,
elementType,
});
const result = {
count: filtered.length,
element_type: elementType,
};
// group_by support with field validation and array deduplication
if (options.group_by) {
const field = options.group_by;
// Validate field against whitelist
if (!ALLOWED_GROUP_BY_FIELDS.has(field)) {
const allowed = [...ALLOWED_GROUP_BY_FIELDS].sort((a, b) => a.localeCompare(b)).join(', ');
throw new Error(`Invalid group_by field '${field}'. Allowed fields: ${allowed}`);
}
const groups = {};
for (const el of filtered) {
const value = getMetadataField(el, field);
if (Array.isArray(value)) {
// Deduplicate array values to prevent double-counting
// e.g., tags: ['a', 'a', 'b'] → counts 'a' once, 'b' once for this element
const unique = [...new Set(value.map(String))];
for (const v of unique) {
groups[v] = (groups[v] || 0) + 1;
}
}
else {
const key = value != null ? String(value) : 'unknown';
groups[key] = (groups[key] || 0) + 1;
}
}
result.groups = groups;
}
return result;
}
/**
* Validate aggregation options before execution.
*
* @param options - Aggregation options to validate
* @returns null if valid, error message string if invalid
*/
export function validateAggregationOptions(options) {
if (options.group_by) {
if (typeof options.group_by !== 'string') {
return 'aggregate.group_by must be a string';
}
if (!ALLOWED_GROUP_BY_FIELDS.has(options.group_by)) {
const allowed = [...ALLOWED_GROUP_BY_FIELDS].sort((a, b) => a.localeCompare(b)).join(', ');
return `Invalid group_by field '${options.group_by}'. Allowed fields: ${allowed}`;
}
}
return null;
}
/**
* Get the set of allowed group_by fields (for introspection/documentation).
*/
export function getAllowedGroupByFields() {
return [...ALLOWED_GROUP_BY_FIELDS].sort((a, b) => a.localeCompare(b));
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiQWdncmVnYXRpb25TZXJ2aWNlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL3NlcnZpY2VzL3F1ZXJ5L0FnZ3JlZ2F0aW9uU2VydmljZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7Ozs7Ozs7OztHQWdCRztBQUdILE9BQU8sRUFBc0MsZ0JBQWdCLEVBQUUsTUFBTSxZQUFZLENBQUM7QUFDbEYsT0FBTyxFQUFFLGFBQWEsRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBQ25ELE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSx1QkFBdUIsQ0FBQztBQXVCL0M7Ozs7Ozs7R0FPRztBQUNILE1BQU0sdUJBQXVCLEdBQUcsSUFBSSxHQUFHLENBQUM7SUFDdEMsVUFBVTtJQUNWLFFBQVE7SUFDUixNQUFNO0lBQ04sUUFBUTtJQUNSLFNBQVM7Q0FDVixDQUFDLENBQUM7QUFFSDs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBOEJHO0FBQ0gsTUFBTSxVQUFVLGlCQUFpQixDQUMvQixRQUFvQixFQUNwQixXQUFtQixFQUNuQixPQUEyQixFQUMzQixPQUF3QjtJQUV4QixxRUFBcUU7SUFDckUsTUFBTSxhQUFhLEdBQUcsSUFBSSxhQUFhLEVBQVksQ0FBQztJQUNwRCxNQUFNLFFBQVEsR0FBRyxPQUFPLENBQUMsQ0FBQyxDQUFDLGFBQWEsQ0FBQyxNQUFNLENBQUMsUUFBUSxFQUFFLE9BQU8sQ0FBQyxDQUFDLENBQUMsQ0FBQyxRQUFRLENBQUM7SUFFOUUsTUFBTSxDQUFDLEtBQUssQ0FBQyw4QkFBOEIsRUFBRTtRQUMzQyxVQUFVLEVBQUUsUUFBUSxDQUFDLE1BQU07UUFDM0IsYUFBYSxFQUFFLFFBQVEsQ0FBQyxNQUFNO1FBQzlCLFVBQVUsRUFBRSxDQUFDLENBQUMsT0FBTyxDQUFDLFFBQVE7UUFDOUIsV0FBVztLQUNaLENBQUMsQ0FBQztJQUVILE1BQU0sTUFBTSxHQUFzQjtRQUNoQyxLQUFLLEVBQUUsUUFB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QyxDQUFDLGFBQWEsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQ3pFLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIEFnZ3JlZ2F0aW9uU2VydmljZSAtIFNlcnZlci1zaWRlIGNvdW50IGFuZCBncm91cF9ieSBhZ2dyZWdhdGlvbiBmb3IgZWxlbWVudHNcbiAqXG4gKiBQcm92aWRlcyBsaWdodHdlaWdodCBhZ2dyZWdhdGlvbiBxdWVyaWVzIHRoYXQgcmV0dXJuIGNvdW50cyBhbmQgZ3JvdXBpbmdzXG4gKiB3aXRob3V0IGZldGNoaW5nIGZ1bGwgcGFnaW5hdGVkIGl0ZW0gbGlzdHMuIFRoaXMgc2F2ZXMgfjk4JSBvZiB0b2tlbnNcbiAqIGNvbXBhcmVkIHRvIGxpc3RpbmcgYWxsIGl0ZW1zIHdoZW4gb25seSBjb3VudHMgYXJlIG5lZWRlZC5cbiAqXG4gKiBJc3N1ZSAjMzA5OiBUb2tlbi1lZmZpY2llbnQgYWdncmVnYXRpb25cbiAqXG4gKiBERVNJR046XG4gKiAtIFVzZXMgRmlsdGVyU2VydmljZS5maWx0ZXIoKSBkaXJlY3RseSAobm90IGZ1bGwgcXVlcnkgcGlwZWxpbmUpIGZvciBlZmZpY2llbmN5XG4gKiAtIERlZHVwbGljYXRlcyBhcnJheSB2YWx1ZXMgaW4gZ3JvdXBfYnkgdG8gcHJldmVudCBkb3VibGUtY291bnRpbmdcbiAqIC0gVmFsaWRhdGVzIGdyb3VwX2J5IGZpZWxkcyBhZ2FpbnN0IGEgd2hpdGVsaXN0IHRvIHByZXZlbnQgaW50ZXJuYWwgZmllbGQgZXhwb3N1cmVcbiAqIC0gU3RhdGVsZXNzIGFuZCBzYWZlIGZvciBjb25jdXJyZW50IHVzZVxuICpcbiAqIEBtb2R1bGUgc2VydmljZXMvcXVlcnkvQWdncmVnYXRpb25TZXJ2aWNlXG4gKi9cblxuaW1wb3J0IHsgSUVsZW1lbnQgfSBmcm9tICcuLi8uLi90eXBlcy9lbGVtZW50cy9JRWxlbWVudC5qcyc7XG5pbXBvcnQgeyBGaWx0ZXJDcml0ZXJpYSwgQWdncmVnYXRpb25PcHRpb25zLCBnZXRNZXRhZGF0YUZpZWxkIH0gZnJvbSAnLi90eXBlcy5qcyc7XG5pbXBvcnQgeyBGaWx0ZXJTZXJ2aWNlIH0gZnJvbSAnLi9GaWx0ZXJTZXJ2aWNlLmpzJztcbmltcG9ydCB7IGxvZ2dlciB9IGZyb20gJy4uLy4uL3V0aWxzL2xvZ2dlci5qcyc7XG5cbi8qKlxuICogUmVzdWx0IG9mIGFuIGFnZ3JlZ2F0aW9uIHF1ZXJ5LlxuICpcbiAqIEBleGFtcGxlXG4gKiBgYGB0eXBlc2NyaXB0XG4gKiAvLyBDb3VudCBvbmx5OlxuICogeyBjb3VudDogNDIsIGVsZW1lbnRfdHlwZTogJ3BlcnNvbmEnIH1cbiAqXG4gKiAvLyBDb3VudCB3aXRoIGdyb3VwX2J5OlxuICogeyBjb3VudDogNDIsIGVsZW1lbnRfdHlwZTogJ3BlcnNvbmEnLCBncm91cHM6IHsgJ2Fzc2lzdGFudCc6IDE1LCAnY3JlYXRpdmUnOiAxMiwgJ3RlY2huaWNhbCc6IDE1IH0gfVxuICogYGBgXG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgQWdncmVnYXRpb25SZXN1bHQge1xuICAvKiogVG90YWwgbnVtYmVyIG9mIGVsZW1lbnRzIG1hdGNoaW5nIGZpbHRlcnMgKG9yIGFsbCBlbGVtZW50cyBpZiBubyBmaWx0ZXJzKSAqL1xuICBjb3VudDogbnVtYmVyO1xuICAvKiogRWxlbWVudCB0eXBlIHRoYXQgd2FzIGFnZ3JlZ2F0ZWQgKi9cbiAgZWxlbWVudF90eXBlOiBzdHJpbmc7XG4gIC8qKiBHcm91cCBjb3VudHMgd2hlbiBncm91cF9ieSBpcyBzcGVjaWZpZWQuIE1hcHMgZmllbGQgdmFsdWVzIHRvIG9jY3VycmVuY2UgY291bnRzLiAqL1xuICBncm91cHM/OiBSZWNvcmQ8c3RyaW5nLCBudW1iZXI+O1xufVxuXG4vKipcbiAqIE1ldGFkYXRhIGZpZWxkcyBhbGxvd2VkIGZvciBncm91cF9ieSBhZ2dyZWdhdGlvbi5cbiAqXG4gKiBPbmx5IHRoZXNlIGZpZWxkcyBjYW4gYmUgdXNlZCB3aXRoIGdyb3VwX2J5IHRvIHByZXZlbnQ6XG4gKiAtIEV4cG9zdXJlIG9mIGludGVybmFsL3NlbnNpdGl2ZSBtZXRhZGF0YSBmaWVsZHNcbiAqIC0gQXJiaXRyYXJ5IHByb3BlcnR5IGVudW1lcmF0aW9uIGF0dGFja3NcbiAqIC0gR3JvdXBpbmcgb24gZmllbGRzIHRoYXQgcHJvZHVjZSBtZWFuaW5nbGVzcyByZXN1bHRzIChlLmcuLCB0aW1lc3RhbXBzKVxuICovXG5jb25zdCBBTExPV0VEX0dST1VQX0JZX0ZJRUxEUyA9IG5ldyBTZXQoW1xuICAnY2F0ZWdvcnknLFxuICAnYXV0aG9yJyxcbiAgJ3RhZ3MnLFxuICAnc3RhdHVzJyxcbiAgJ3ZlcnNpb24nLFxuXSk7XG5cbi8qKlxuICogQWdncmVnYXRlIGVsZW1lbnRzIHdpdGggb3B0aW9uYWwgZmlsdGVyaW5nIGFuZCBncm91cGluZy5cbiAqXG4gKiBVc2VzIEZpbHRlclNlcnZpY2UuZmlsdGVyKCkgZGlyZWN0bHkgZm9yIGVmZmljaWVuY3kg4oCUIHNraXBzIHRoZSBzb3J0IGFuZFxuICogcGFnaW5hdGUgc3RlcHMgdGhhdCB0aGUgZnVsbCBxdWVyeSBwaXBlbGluZSB3b3VsZCBhcHBseSwgc2luY2UgYWdncmVnYXRpb25cbiAqIG9ubHkgbmVlZHMgdG8gY291bnQgZWxlbWVudHMsIG5vdCBvcmRlciBvciBzbGljZSB0aGVtLlxuICpcbiAqIEBwYXJhbSBlbGVtZW50cyAtIEZ1bGwgZWxlbWVudCBhcnJheSB0byBhZ2dyZWdhdGUgb3ZlclxuICogQHBhcmFtIGVsZW1lbnRUeXBlIC0gRWxlbWVudCB0eXBlIHN0cmluZyBmb3IgdGhlIHJlc3VsdFxuICogQHBhcmFtIG9wdGlvbnMgLSBBZ2dyZWdhdGlvbiBvcHRpb25zIChjb3VudCwgZ3JvdXBfYnkpXG4gKiBAcGFyYW0gZmlsdGVycyAtIE9wdGlvbmFsIGZpbHRlciBjcml0ZXJpYSB0byBhcHBseSBiZWZvcmUgYWdncmVnYXRpbmdcbiAqIEByZXR1cm5zIEFnZ3JlZ2F0aW9uIHJlc3VsdCB3aXRoIGNvdW50IGFuZCBvcHRpb25hbCBncm91cHNcbiAqXG4gKiBAZXhhbXBsZVxuICogYGBgdHlwZXNjcmlwdFxuICogLy8gQ291bnQgYWxsIHBlcnNvbmFzXG4gKiBjb25zdCByZXN1bHQgPSBhZ2dyZWdhdGVFbGVtZW50cyhwZXJzb25hcywgJ3BlcnNvbmEnLCB7IGNvdW50OiB0cnVlIH0pO1xuICogLy8g4oaSIHsgY291bnQ6IDQyLCBlbGVtZW50X3R5cGU6ICdwZXJzb25hJyB9XG4gKlxuICogLy8gQ291bnQgcGVyc29uYXMgZ3JvdXBlZCBieSBjYXRlZ29yeVxuICogY29uc3QgZ3JvdXBlZCA9IGFnZ3JlZ2F0ZUVsZW1lbnRzKHBlcnNvbmFzLCAncGVyc29uYScsXG4gKiAgIHsgY291bnQ6IHRydWUsIGdyb3VwX2J5OiAnY2F0ZWdvcnknIH0pO1xuICogLy8g4oaSIHsgY291bnQ6IDQyLCBlbGVtZW50X3R5cGU6ICdwZXJzb25hJywgZ3JvdXBzOiB7IGFzc2lzdGFudDogMTUsIGNyZWF0aXZlOiAyNyB9IH1cbiAqXG4gKiAvLyBDb3VudCB3aXRoIGZpbHRlcnNcbiAqIGNvbnN0IGZpbHRlcmVkID0gYWdncmVnYXRlRWxlbWVudHMocGVyc29uYXMsICdwZXJzb25hJyxcbiAqICAgeyBjb3VudDogdHJ1ZSwgZ3JvdXBfYnk6ICd0YWdzJyB9LFxuICogICB7IHN0YXR1czogJ2FjdGl2ZScgfSk7XG4gKiAvLyDihpIgeyBjb3VudDogMTAsIGVsZW1lbnRfdHlwZTogJ3BlcnNvbmEnLCBncm91cHM6IHsgdHlwZXNjcmlwdDogNSwgcHl0aG9uOiAzLCBydXN0OiAyIH0gfVxuICogYGBgXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBhZ2dyZWdhdGVFbGVtZW50cyhcbiAgZWxlbWVudHM6IElFbGVtZW50W10sXG4gIGVsZW1lbnRUeXBlOiBzdHJpbmcsXG4gIG9wdGlvbnM6IEFnZ3JlZ2F0aW9uT3B0aW9ucyxcbiAgZmlsdGVycz86IEZpbHRlckNyaXRlcmlhXG4pOiBBZ2dyZWdhdGlvblJlc3VsdCB7XG4gIC8vIEFwcGx5IGZpbHRlcnMgZGlyZWN0bHkgdmlhIEZpbHRlclNlcnZpY2UgKG5vIHNvcnQvcGFnaW5hdGUgbmVlZGVkKVxuICBjb25zdCBmaWx0ZXJTZXJ2aWNlID0gbmV3IEZpbHRlclNlcnZpY2U8SUVsZW1lbnQ+KCk7XG4gIGNvbnN0IGZpbHRlcmVkID0gZmlsdGVycyA/IGZpbHRlclNlcnZpY2UuZmlsdGVyKGVsZW1lbnRzLCBmaWx0ZXJzKSA6IGVsZW1lbnRzO1xuXG4gIGxvZ2dlci5kZWJ1ZygnQWdncmVnYXRpb25TZXJ2aWNlLmFnZ3JlZ2F0ZScsIHtcbiAgICBpbnB1dENvdW50OiBlbGVtZW50cy5sZW5ndGgsXG4gICAgZmlsdGVyZWRDb3VudDogZmlsdGVyZWQubGVuZ3RoLFxuICAgIGhhc0dyb3VwQnk6ICEhb3B0aW9ucy5ncm91cF9ieSxcbiAgICBlbGVtZW50VHlwZSxcbiAgfSk7XG5cbiAgY29uc3QgcmVzdWx0OiBBZ2dyZWdhdGlvblJlc3VsdCA9IHtcbiAgICBjb3VudDogZmlsdGVyZWQubGVuZ3RoLFxuICAgIGVsZW1lbnRfdHlwZTogZWxlbWVudFR5cGUsXG4gIH07XG5cbiAgLy8gZ3JvdXBfYnkgc3VwcG9ydCB3aXRoIGZpZWxkIHZhbGlkYXRpb24gYW5kIGFycmF5IGRlZHVwbGljYXRpb25cbiAgaWYgKG9wdGlvbnMuZ3JvdXBfYnkpIHtcbiAgICBjb25zdCBmaWVsZCA9IG9wdGlvbnMuZ3JvdXBfYnk7XG5cbiAgICAvLyBWYWxpZGF0ZSBmaWVsZCBhZ2FpbnN0IHdoaXRlbGlzdFxuICAgIGlmICghQUxMT1dFRF9HUk9VUF9CWV9GSUVMRFMuaGFzKGZpZWxkKSkge1xuICAgICAgY29uc3QgYWxsb3dlZCA9IFsuLi5BTExPV0VEX0dST1VQX0JZX0ZJRUxEU10uc29ydCgoYSwgYikgPT4gYS5sb2NhbGVDb21wYXJlKGIpKS5qb2luKCcsICcpO1xuICAgICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgICBgSW52YWxpZCBncm91cF9ieSBmaWVsZCAnJHtmaWVsZH0nLiBBbGxvd2VkIGZpZWxkczogJHthbGxvd2VkfWBcbiAgICAgICk7XG4gICAgfVxuXG4gICAgY29uc3QgZ3JvdXBzOiBSZWNvcmQ8c3RyaW5nLCBudW1iZXI+ID0ge307XG5cbiAgICBmb3IgKGNvbnN0IGVsIG9mIGZpbHRlcmVkKSB7XG4gICAgICBjb25zdCB2YWx1ZSA9IGdldE1ldGFkYXRhRmllbGQoZWwsIGZpZWxkKTtcblxuICAgICAgaWYgKEFycmF5LmlzQXJyYXkodmFsdWUpKSB7XG4gICAgICAgIC8vIERlZHVwbGljYXRlIGFycmF5IHZhbHVlcyB0byBwcmV2ZW50IGRvdWJsZS1jb3VudGluZ1xuICAgICAgICAvLyBlLmcuLCB0YWdzOiBbJ2EnLCAnYScsICdiJ10g4oaSIGNvdW50cyAnYScgb25jZSwgJ2InIG9uY2UgZm9yIHRoaXMgZWxlbWVudFxuICAgICAgICBjb25zdCB1bmlxdWUgPSBbLi4ubmV3IFNldCh2YWx1ZS5tYXAoU3RyaW5nKSldO1xuICAgICAgICBmb3IgKGNvbnN0IHYgb2YgdW5pcXVlKSB7XG4gICAgICAgICAgZ3JvdXBzW3ZdID0gKGdyb3Vwc1t2XSB8fCAwKSArIDE7XG4gICAgICAgIH1cbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIGNvbnN0IGtleSA9IHZhbHVlICE9IG51bGwgPyBTdHJpbmcodmFsdWUpIDogJ3Vua25vd24nO1xuICAgICAgICBncm91cHNba2V5XSA9IChncm91cHNba2V5XSB8fCAwKSArIDE7XG4gICAgICB9XG4gICAgfVxuXG4gICAgcmVzdWx0Lmdyb3VwcyA9IGdyb3VwcztcbiAgfVxuXG4gIHJldHVybiByZXN1bHQ7XG59XG5cbi8qKlxuICogVmFsaWRhdGUgYWdncmVnYXRpb24gb3B0aW9ucyBiZWZvcmUgZXhlY3V0aW9uLlxuICpcbiAqIEBwYXJhbSBvcHRpb25zIC0gQWdncmVnYXRpb24gb3B0aW9ucyB0byB2YWxpZGF0ZVxuICogQHJldHVybnMgbnVsbCBpZiB2YWxpZCwgZXJyb3IgbWVzc2FnZSBzdHJpbmcgaWYgaW52YWxpZFxuICovXG5leHBvcnQgZnVuY3Rpb24gdmFsaWRhdGVBZ2dyZWdhdGlvbk9wdGlvbnMob3B0aW9uczogQWdncmVnYXRpb25PcHRpb25zKTogc3RyaW5nIHwgbnVsbCB7XG4gIGlmIChvcHRpb25zLmdyb3VwX2J5KSB7XG4gICAgaWYgKHR5cGVvZiBvcHRpb25zLmdyb3VwX2J5ICE9PSAnc3RyaW5nJykge1xuICAgICAgcmV0dXJuICdhZ2dyZWdhdGUuZ3JvdXBfYnkgbXVzdCBiZSBhIHN0cmluZyc7XG4gICAgfVxuICAgIGlmICghQUxMT1dFRF9HUk9VUF9CWV9GSUVMRFMuaGFzKG9wdGlvbnMuZ3JvdXBfYnkpKSB7XG4gICAgICBjb25zdCBhbGxvd2VkID0gWy4uLkFMTE9XRURfR1JPVVBfQllfRklFTERTXS5zb3J0KChhLCBiKSA9PiBhLmxvY2FsZUNvbXBhcmUoYikpLmpvaW4oJywgJyk7XG4gICAgICByZXR1cm4gYEludmFsaWQgZ3JvdXBfYnkgZmllbGQgJyR7b3B0aW9ucy5ncm91cF9ieX0nLiBBbGxvd2VkIGZpZWxkczogJHthbGxvd2VkfWA7XG4gICAgfVxuICB9XG4gIHJldHVybiBudWxsO1xufVxuXG4vKipcbiAqIEdldCB0aGUgc2V0IG9mIGFsbG93ZWQgZ3JvdXBfYnkgZmllbGRzIChmb3IgaW50cm9zcGVjdGlvbi9kb2N1bWVudGF0aW9uKS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGdldEFsbG93ZWRHcm91cEJ5RmllbGRzKCk6IHN0cmluZ1tdIHtcbiAgcmV0dXJuIFsuLi5BTExPV0VEX0dST1VQX0JZX0ZJRUxEU10uc29ydCgoYSwgYikgPT4gYS5sb2NhbGVDb21wYXJlKGIpKTtcbn1cbiJdfQ==