mono/packages/ai-tools/dist/lib/tools/memory.js
2026-03-19 17:39:41 +01:00

354 lines
26 KiB
JavaScript

import * as path from 'path';
import { toolLogger } from '../../index.js';
import { store, get, set } from './keyv.js';
// Helper function to get storage path
const getStoragePath = (options) => {
// For now, use default path. Later this can be configured via options
return path.join(process.cwd(), 'memory.json');
};
// Default collection name when none provided
const DEFAULT_COLLECTION = 'no-collection';
// Helper function to process value based on format
const processValueForStorage = (value, format) => {
switch (format) {
case 'json':
try {
// Validate JSON by parsing and re-stringifying
JSON.parse(value);
return value;
}
catch (error) {
throw new Error('Invalid JSON format provided');
}
case 'binary':
// For binary, we expect base64 encoded data
try {
// Validate base64
Buffer.from(value, 'base64');
return value;
}
catch (error) {
throw new Error('Invalid base64 format for binary data');
}
case 'text':
default:
return value;
}
};
// Helper function to create memory entry
const createMemoryEntry = (value, format) => {
const now = new Date().toISOString();
return {
value: processValueForStorage(value, format),
meta: {
type: format,
created: now,
updated: now
}
};
};
export const tools = (target, options) => {
const logger = toolLogger('memory', options);
const storagePath = getStoragePath(options);
return [
{
type: 'function',
function: {
name: 'memorize',
description: `Store information in memory as a key-value collection with format support. Supports text, JSON, and binary (base64) formats.
Returns: {
success: boolean,
message: string,
meta: {
type: "text" | "json" | "binary",
created: string (ISO timestamp),
updated: string (ISO timestamp)
}
}`,
parameters: {
type: 'object',
properties: {
collection: {
type: 'string',
description: 'Collection name to organize related data (defaults to "no-collection" if not provided). Acts like a namespace.',
optional: true
},
key: {
type: 'string',
description: 'Unique identifier for the data within the collection. Must be a string.'
},
value: {
type: 'string',
description: 'The data to store. For format="text": any string. For format="json": valid JSON string. For format="binary": base64 encoded data.'
},
format: {
type: 'string',
description: 'Data format type. "text" for plain text (default), "json" for JSON data (validates structure), "binary" for base64 encoded binary data.',
enum: ['text', 'json', 'binary'],
optional: true
}
},
required: ['key', 'value']
},
function: async (params) => {
try {
const { collection = DEFAULT_COLLECTION, key, value, format = 'text' } = params;
logger.debug(`Tool::Memorize Storing ${key} in collection ${collection} as ${format}`);
const memoryEntry = createMemoryEntry(value, format);
await set(`${collection}:${key}`, memoryEntry, storagePath, collection);
return {
success: true,
message: `Stored ${key} in collection ${collection} as ${format}`,
meta: memoryEntry.meta
};
}
catch (error) {
logger.error('Error storing memory', error);
return {
success: false,
message: error instanceof Error ? error.message : 'Unknown error occurred'
};
}
},
parse: JSON.parse
}
},
{
type: 'function',
function: {
name: 'recall',
description: `Retrieve stored information from memory by collection and key, including format metadata.
Returns: {
success: boolean,
value?: string (the stored data),
meta?: {
type: "text" | "json" | "binary",
created: string (ISO timestamp),
updated: string (ISO timestamp)
},
key: string,
collection: string,
message?: string (error message if success=false)
}`,
parameters: {
type: 'object',
properties: {
collection: {
type: 'string',
description: 'Collection name to retrieve from (defaults to "no-collection" if not provided). Must match the collection used when storing.',
optional: true
},
key: {
type: 'string',
description: 'The unique identifier of the data to retrieve. Must match the key used when storing.'
}
},
required: ['key']
},
function: async (params) => {
try {
const { collection = DEFAULT_COLLECTION, key } = params;
logger.debug(`Tool::Recall Retrieving ${key} from collection ${collection}`);
const storedData = await get(`${collection}:${key}`, storagePath, collection);
if (storedData === undefined) {
return { success: false, message: `Key ${key} not found in collection ${collection}` };
}
// Handle both old format (plain string) and new format (MemoryEntry)
let memoryEntry;
if (typeof storedData === 'string') {
// Legacy format - convert to new format
memoryEntry = {
value: storedData,
meta: {
type: 'text',
created: new Date().toISOString(),
updated: new Date().toISOString()
}
};
}
else {
memoryEntry = storedData;
}
return {
success: true,
value: memoryEntry.value,
meta: memoryEntry.meta,
key,
collection
};
}
catch (error) {
logger.error('Error retrieving memory', error);
return {
success: false,
message: error instanceof Error ? error.message : 'Unknown error occurred'
};
}
},
parse: JSON.parse
}
},
{
type: 'function',
function: {
name: 'forget',
description: `Remove a specific key from memory collection.
Returns: {
success: boolean,
message: string (confirmation or error message)
}`,
parameters: {
type: 'object',
properties: {
collection: {
type: 'string',
description: 'Collection name to remove from (defaults to "no-collection" if not provided). Must match the collection where the key was stored.',
optional: true
},
key: {
type: 'string',
description: 'The unique identifier of the data to remove. Must match exactly the key used when storing.'
}
},
required: ['key']
},
function: async (params) => {
try {
const { collection = DEFAULT_COLLECTION, key } = params;
logger.debug(`Tool::Forget Removing ${key} from collection ${collection}`);
const keyv = store(storagePath, collection);
const deleted = await keyv.delete(`${collection}:${key}`);
return { success: deleted, message: deleted ? `Removed ${key} from ${collection}` : `Key ${key} not found in ${collection}` };
}
catch (error) {
logger.error('Error removing from memory', error);
return {
success: false,
message: error instanceof Error ? error.message : 'Unknown error occurred'
};
}
},
parse: JSON.parse
}
},
{
type: 'function',
function: {
name: 'list_memories',
description: `List all keys in a specific collection using Keyv's iterator method.
Returns: {
success: boolean,
collection: string (the collection name),
keys: string[] (array of key names in the collection),
entries: Array<{
key: string,
meta?: {
type: "text" | "json" | "binary",
created: string (ISO timestamp),
updated: string (ISO timestamp)
}
}>,
count: number (total number of keys),
message?: string (info or error message)
}`,
parameters: {
type: 'object',
properties: {
collection: {
type: 'string',
description: 'Collection name to list keys from (defaults to "no-collection" if not provided). Will return all keys stored in this collection namespace.',
optional: true
}
},
required: []
},
function: async (params) => {
try {
const { collection = DEFAULT_COLLECTION } = params;
logger.debug(`Tool::ListMemories Listing keys in collection ${collection}`);
// Create a Keyv instance for the specific collection to use iterator
const keyv = store(storagePath, collection);
const keys = [];
const entries = [];
try {
// Check if iterator method exists and use it
if (typeof keyv.iterator === 'function') {
try {
// Try calling iterator without arguments first
const iterator = keyv.iterator();
for await (const [key, value] of iterator) {
// Remove the collection prefix from the key to get the clean key name
const cleanKey = key.replace(`${collection}:`, '');
keys.push(cleanKey);
// Try to extract metadata if it's a MemoryEntry
let meta = undefined;
if (value && typeof value === 'object' && value.meta) {
meta = value.meta;
}
entries.push({ key: cleanKey, meta });
}
}
catch (iteratorCallError) {
logger.warn(`Tool::ListMemories Iterator call failed:`, iteratorCallError);
// Fall through to the not available case
return {
success: true,
collection,
keys: [],
entries: [],
count: 0,
message: 'Iterator call failed. Unable to list keys.'
};
}
}
else {
// Iterator not available, provide helpful message
logger.warn(`Tool::ListMemories Iterator method not available for Keyv instance`);
return {
success: true,
collection,
keys: [],
entries: [],
count: 0,
message: 'Iterator method not available in this Keyv version. Use individual key operations instead.'
};
}
return {
success: true,
collection,
keys,
entries,
count: keys.length
};
}
catch (iteratorError) {
// If iterator fails, fall back to returning basic info
logger.warn(`Tool::ListMemories Iterator failed for collection ${collection}:`, iteratorError);
return {
success: true,
collection,
keys: [],
entries: [],
count: 0,
message: 'Iterator failed or collection is empty'
};
}
}
catch (error) {
logger.error('Error listing memories', error);
return {
success: false,
message: error instanceof Error ? error.message : 'Unknown error occurred'
};
}
},
parse: JSON.parse
}
}
];
};
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWVtb3J5LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL2xpYi90b29scy9tZW1vcnkudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLElBQUksTUFBTSxNQUFNLENBQUE7QUFJNUIsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLGdCQUFnQixDQUFBO0FBRzNDLE9BQU8sRUFBRSxLQUFLLEVBQUUsR0FBRyxFQUFFLEdBQUcsRUFBRSxNQUFNLFdBQVcsQ0FBQTtBQUUzQyxzQ0FBc0M7QUFDdEMsTUFBTSxjQUFjLEdBQUcsQ0FBQyxPQUFrQixFQUFVLEVBQUU7SUFDbEQsc0VBQXNFO0lBQ3RFLE9BQU8sSUFBSSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsR0FBRyxFQUFFLEVBQUUsYUFBYSxDQUFDLENBQUM7QUFDbkQsQ0FBQyxDQUFDO0FBRUYsNkNBQTZDO0FBQzdDLE1BQU0sa0JBQWtCLEdBQUcsZUFBZSxDQUFDO0FBZTNDLG1EQUFtRDtBQUNuRCxNQUFNLHNCQUFzQixHQUFHLENBQUMsS0FBYSxFQUFFLE1BQWtCLEVBQVUsRUFBRTtJQUN6RSxRQUFRLE1BQU0sRUFBRSxDQUFDO1FBQ2IsS0FBSyxNQUFNO1lBQ1AsSUFBSSxDQUFDO2dCQUNELCtDQUErQztnQkFDL0MsSUFBSSxDQUFDLEtBQUssQ0FBQyxLQUFLLENBQUMsQ0FBQztnQkFDbEIsT0FBTyxLQUFLLENBQUM7WUFDakIsQ0FBQztZQUFDLE9BQU8sS0FBSyxFQUFFLENBQUM7Z0JBQ2IsTUFBTSxJQUFJLEtBQUssQ0FBQyw4QkFBOEIsQ0FBQyxDQUFDO1lBQ3BELENBQUM7UUFDTCxLQUFLLFFBQVE7WUFDVCw0Q0FBNEM7WUFDNUMsSUFBSSxDQUFDO2dCQUNELGtCQUFrQjtnQkFDbEIsTUFBTSxDQUFDLElBQUksQ0FBQyxLQUFLLEVBQUUsUUFBUSxDQUFDLENBQUM7Z0JBQzdCLE9BQU8sS0FBSyxDQUFDO1lBQ2pCLENBQUM7WUFBQyxPQUFPLEtBQUssRUFBRSxDQUFDO2dCQUNiLE1BQU0sSUFBSSxLQUFLLENBQUMsdUNBQXVDLENBQUMsQ0FBQztZQUM3RCxDQUFDO1FBQ0wsS0FBSyxNQUFNLENBQUM7UUFDWjtZQUNJLE9BQU8sS0FBSyxDQUFDO0lBQ3JCLENBQUM7QUFDTCxDQUFDLENBQUM7QUFFRix5Q0FBeUM7QUFDekMsTUFBTSxpQkFBaUIsR0FBRyxDQUFDLEtBQWEsRUFBRSxNQUFrQixFQUFlLEVBQUU7SUFDekUsTUFBTSxHQUFHLEdBQUcsSUFBSSxJQUFJLEVBQUUsQ0FBQyxXQUFXLEVBQUUsQ0FBQztJQUNyQyxPQUFPO1FBQ0gsS0FBSyxFQUFFLHNCQUFzQixDQUFDLEtBQUssRUFBRSxNQUFNLENBQUM7UUFDNUMsSUFBSSxFQUFFO1lBQ0YsSUFBSSxFQUFFLE1BQU07WUFDWixPQUFPLEVBQUUsR0FBRztZQUNaLE9BQU8sRUFBRSxHQUFHO1NBQ2Y7S0FDSixDQUFDO0FBQ04sQ0FBQyxDQUFDO0FBRUYsTUFBTSxDQUFDLE1BQU0sS0FBSyxHQUFHLENBQUMsTUFBYyxFQUFFLE9BQWtCLEVBQWMsRUFBRTtJQUNwRSxNQUFNLE1BQU0sR0FBRyxVQUFVLENBQUMsUUFBUSxFQUFFLE9BQU8sQ0FBQyxDQUFBO0lBQzVDLE1BQU0sV0FBVyxHQUFHLGNBQWMsQ0FBQyxPQUFPLENBQUMsQ0FBQztJQUU1QyxPQUFPO1FBQ0g7WUFDSSxJQUFJLEVBQUUsVUFBVTtZQUNoQixRQUFRLEVBQUU7Z0JBQ04sSUFBSSxFQUFFLFVBQVU7Z0JBQ2hCLFdBQVcsRUFBRTs7Ozs7Ozs7OztFQVUzQjtnQkFDYyxVQUFVLEVBQUU7b0JBQ1IsSUFBSSxFQUFFLFFBQVE7b0JBQ2QsVUFBVSxFQUFFO3dCQUNSLFVBQVUsRUFBRTs0QkFDUixJQUFJLEVBQUUsUUFBUTs0QkFDZCxXQUFXLEVBQUUsZ0hBQWdIOzRCQUM3SCxRQUFRLEVBQUUsSUFBSTt5QkFDakI7d0JBQ0QsR0FBRyxFQUFFOzRCQUNELElBQUksRUFBRSxRQUFROzRCQUNkLFdBQVcsRUFBRSx5RUFBeUU7eUJBQ3pGO3dCQUNELEtBQUssRUFBRTs0QkFDSCxJQUFJLEVBQUUsUUFBUTs0QkFDZCxXQUFXLEVBQUUsbUlBQW1JO3lCQUNuSjt3QkFDRCxNQUFNLEVBQUU7NEJBQ0osSUFBSSxFQUFFLFFBQVE7NEJBQ2QsV0FBVyxFQUFFLHlJQUF5STs0QkFDdEosSUFBSSxFQUFFLENBQUMsTUFBTSxFQUFFLE1BQU0sRUFBRSxRQUFRLENBQUM7NEJBQ2hDLFFBQVEsRUFBRSxJQUFJO3lCQUNqQjtxQkFDSjtvQkFDRCxRQUFRLEVBQUUsQ0FBQyxLQUFLLEVBQUUsT0FBTyxDQUFDO2lCQUM3QjtnQkFDRCxRQUFRLEVBQUUsS0FBSyxFQUFFLE1BQVcsRUFBRSxFQUFFO29CQUM1QixJQUFJLENBQUM7d0JBQ0QsTUFBTSxFQUFFLFVBQVUsR0FBRyxrQkFBa0IsRUFBRSxHQUFHLEVBQUUsS0FBSyxFQUFFLE1BQU0sR0FBRyxNQUFNLEVBQUUsR0FBRyxNQUFNLENBQUM7d0JBQ2hGLE1BQU0sQ0FBQyxLQUFLLENBQUMsMEJBQTBCLEdBQUcsa0JBQWtCLFVBQVUsT0FBTyxNQUFNLEVBQUUsQ0FBQyxDQUFDO3dCQUV2RixNQUFNLFdBQVcsR0FBRyxpQkFBaUIsQ0FBQyxLQUFLLEVBQUUsTUFBb0IsQ0FBQyxDQUFDO3dCQUNuRSxNQUFNLEdBQUcsQ0FBQyxHQUFHLFVBQVUsSUFBSSxHQUFHLEVBQUUsRUFBRSxXQUFXLEVBQUUsV0FBVyxFQUFFLFVBQVUsQ0FBQyxDQUFDO3dCQUV4RSxPQUFPOzRCQUNILE9BQU8sRUFBRSxJQUFJOzRCQUNiLE9BQU8sRUFBRSxVQUFVLEdBQUcsa0JBQWtCLFVBQVUsT0FBTyxNQUFNLEVBQUU7NEJBQ2pFLElBQUksRUFBRSxXQUFXLENBQUMsSUFBSTt5QkFDekIsQ0FBQztvQkFDTixDQUFDO29CQUFDLE9BQU8sS0FBSyxFQUFFLENBQUM7d0JBQ2IsTUFBTSxDQUFDLEtBQUssQ0FBQyxzQkFBc0IsRUFBRSxLQUFLLENBQUMsQ0FBQzt3QkFDNUMsT0FBTzs0QkFDSCxPQUFPLEVBQUUsS0FBSzs0QkFDZCxPQUFPLEVBQUUsS0FBSyxZQUFZLEtBQUssQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsd0JBQXdCO3lCQUM3RSxDQUFDO29CQUNOLENBQUM7Z0JBQ0wsQ0FBQztnQkFDRCxLQUFLLEVBQUUsSUFBSSxDQUFDLEtBQUs7YUFDcEI7U0FDeUI7UUFDOUI7WUFDSSxJQUFJLEVBQUUsVUFBVTtZQUNoQixRQUFRLEVBQUU7Z0JBQ04sSUFBSSxFQUFFLFFBQVE7Z0JBQ2QsV0FBVyxFQUFFOzs7Ozs7Ozs7Ozs7O0VBYTNCO2dCQUNjLFVBQVUsRUFBRTtvQkFDUixJQUFJLEVBQUUsUUFBUTtvQkFDZCxVQUFVLEVBQUU7d0JBQ1IsVUFBVSxFQUFFOzRCQUNSLElBQUksRUFBRSxRQUFROzRCQUNkLFdBQVcsRUFBRSw4SEFBOEg7NEJBQzNJLFFBQVEsRUFBRSxJQUFJO3lCQUNqQjt3QkFDRCxHQUFHLEVBQUU7NEJBQ0QsSUFBSSxFQUFFLFFBQVE7NEJBQ2QsV0FBVyxFQUFFLHNGQUFzRjt5QkFDdEc7cUJBQ0o7b0JBQ0QsUUFBUSxFQUFFLENBQUMsS0FBSyxDQUFDO2lCQUNwQjtnQkFDRCxRQUFRLEVBQUUsS0FBSyxFQUFFLE1BQVcsRUFBRSxFQUFFO29CQUM1QixJQUFJLENBQUM7d0JBQ0QsTUFBTSxFQUFFLFVBQVUsR0FBRyxrQkFBa0IsRUFBRSxHQUFHLEVBQUUsR0FBRyxNQUFNLENBQUM7d0JBQ3hELE1BQU0sQ0FBQyxLQUFLLENBQUMsMkJBQTJCLEdBQUcsb0JBQW9CLFVBQVUsRUFBRSxDQUFDLENBQUM7d0JBRTdFLE1BQU0sVUFBVSxHQUFHLE1BQU0sR0FBRyxDQUFDLEdBQUcsVUFBVSxJQUFJLEdBQUcsRUFBRSxFQUFFLFdBQVcsRUFBRSxVQUFVLENBQUMsQ0FBQzt3QkFDOUUsSUFBSSxVQUFVLEtBQUssU0FBUyxFQUFFLENBQUM7NEJBQzNCLE9BQU8sRUFBRSxPQUFPLEVBQUUsS0FBSyxFQUFFLE9BQU8sRUFBRSxPQUFPLEdBQUcsNEJBQTRCLFVBQVUsRUFBRSxFQUFFLENBQUM7d0JBQzNGLENBQUM7d0JBRUQscUVBQXFFO3dCQUNyRSxJQUFJLFdBQXdCLENBQUM7d0JBQzdCLElBQUksT0FBTyxVQUFVLEtBQUssUUFBUSxFQUFFLENBQUM7NEJBQ2pDLHdDQUF3Qzs0QkFDeEMsV0FBVyxHQUFHO2dDQUNWLEtBQUssRUFBRSxVQUFVO2dDQUNqQixJQUFJLEVBQUU7b0NBQ0YsSUFBSSxFQUFFLE1BQU07b0NBQ1osT0FBTyxFQUFFLElBQUksSUFBSSxFQUFFLENBQUMsV0FBVyxFQUFFO29DQUNqQyxPQUFPLEVBQUUsSUFBSSxJQUFJLEVBQUUsQ0FBQyxXQUFXLEVBQUU7aUNBQ3BDOzZCQUNKLENBQUM7d0JBQ04sQ0FBQzs2QkFBTSxDQUFDOzRCQUNKLFdBQVcsR0FBRyxVQUF5QixDQUFDO3dCQUM1QyxDQUFDO3dCQUVELE9BQU87NEJBQ0gsT0FBTyxFQUFFLElBQUk7NEJBQ2IsS0FBSyxFQUFFLFdBQVcsQ0FBQyxLQUFLOzRCQUN4QixJQUFJLEVBQUUsV0FBVyxDQUFDLElBQUk7NEJBQ3RCLEdBQUc7NEJBQ0gsVUFBVTt5QkFDYixDQUFDO29CQUNOLENBQUM7b0JBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQzt3QkFDYixNQUFNLENBQUMsS0FBSyxDQUFDLHlCQUF5QixFQUFFLEtBQUssQ0FBQyxDQUFDO3dCQUMvQyxPQUFPOzRCQUNILE9BQU8sRUFBRSxLQUFLOzRCQUNkLE9BQU8sRUFBRSxLQUFLLFlBQVksS0FBSyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyx3QkFBd0I7eUJBQzdFLENBQUM7b0JBQ04sQ0FBQztnQkFDTCxDQUFDO2dCQUNELEtBQUssRUFBRSxJQUFJLENBQUMsS0FBSzthQUNwQjtTQUN5QjtRQUM5QjtZQUNJLElBQUksRUFBRSxVQUFVO1lBQ2hCLFFBQVEsRUFBRTtnQkFDTixJQUFJLEVBQUUsUUFBUTtnQkFDZCxXQUFXLEVBQUU7Ozs7O0VBSzNCO2dCQUNjLFVBQVUsRUFBRTtvQkFDUixJQUFJLEVBQUUsUUFBUTtvQkFDZCxVQUFVLEVBQUU7d0JBQ1IsVUFBVSxFQUFFOzRCQUNSLElBQUksRUFBRSxRQUFROzRCQUNkLFdBQVcsRUFBRSxtSUFBbUk7NEJBQ2hKLFFBQVEsRUFBRSxJQUFJO3lCQUNqQjt3QkFDRCxHQUFHLEVBQUU7NEJBQ0QsSUFBSSxFQUFFLFFBQVE7NEJBQ2QsV0FBVyxFQUFFLDRGQUE0Rjt5QkFDNUc7cUJBQ0o7b0JBQ0QsUUFBUSxFQUFFLENBQUMsS0FBSyxDQUFDO2lCQUNwQjtnQkFDRCxRQUFRLEVBQUUsS0FBSyxFQUFFLE1BQVcsRUFBRSxFQUFFO29CQUM1QixJQUFJLENBQUM7d0JBQ0QsTUFBTSxFQUFFLFVBQVUsR0FBRyxrQkFBa0IsRUFBRSxHQUFHLEVBQUUsR0FBRyxNQUFNLENBQUM7d0JBQ3hELE1BQU0sQ0FBQyxLQUFLLENBQUMseUJBQXlCLEdBQUcsb0JBQW9CLFVBQVUsRUFBRSxDQUFDLENBQUM7d0JBRTNFLE1BQU0sSUFBSSxHQUFHLEtBQUssQ0FBQyxXQUFXLEVBQUUsVUFBVSxDQUFDLENBQUM7d0JBQzVDLE1BQU0sT0FBTyxHQUFHLE1BQU0sSUFBSSxDQUFDLE1BQU0sQ0FBQyxHQUFHLFVBQVUsSUFBSSxHQUFHLEVBQUUsQ0FBQyxDQUFDO3dCQUMxRCxPQUFPLEVBQUUsT0FBTyxFQUFFLE9BQU8sRUFBRSxPQUFPLEVBQUUsT0FBTyxDQUFDLENBQUMsQ0FBQyxXQUFXLEdBQUcsU0FBUyxVQUFVLEVBQUUsQ0FBQyxDQUFDLENBQUMsT0FBTyxHQUFHLGlCQUFpQixVQUFVLEVBQUUsRUFBRSxDQUFDO29CQUNsSSxDQUFDO29CQUFDLE9BQU8sS0FBSyxFQUFFLENBQUM7d0JBQ2IsTUFBTSxDQUFDLEtBQUssQ0FBQyw0QkFBNEIsRUFBRSxLQUFLLENBQUMsQ0FBQzt3QkFDbEQsT0FBTzs0QkFDSCxPQUFPLEVBQUUsS0FBSzs0QkFDZCxPQUFPLEVBQUUsS0FBSyxZQUFZLEtBQUssQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsd0JBQXdCO3lCQUM3RSxDQUFDO29CQUNOLENBQUM7Z0JBQ0wsQ0FBQztnQkFDRCxLQUFLLEVBQUUsSUFBSSxDQUFDLEtBQUs7YUFDcEI7U0FDeUI7UUFDOUI7WUFDSSxJQUFJLEVBQUUsVUFBVTtZQUNoQixRQUFRLEVBQUU7Z0JBQ04sSUFBSSxFQUFFLGVBQWU7Z0JBQ3JCLFdBQVcsRUFBRTs7Ozs7Ozs7Ozs7Ozs7OztFQWdCM0I7Z0JBQ2MsVUFBVSxFQUFFO29CQUNSLElBQUksRUFBRSxRQUFRO29CQUNkLFVBQVUsRUFBRTt3QkFDUixVQUFVLEVBQUU7NEJBQ1IsSUFBSSxFQUFFLFFBQVE7NEJBQ2QsV0FBVyxFQUFFLDRJQUE0STs0QkFDekosUUFBUSxFQUFFLElBQUk7eUJBQ2pCO3FCQUNKO29CQUNELFFBQVEsRUFBRSxFQUFFO2lCQUNmO2dCQUNELFFBQVEsRUFBRSxLQUFLLEVBQUUsTUFBVyxFQUFFLEVBQUU7b0JBQzVCLElBQUksQ0FBQzt3QkFDRCxNQUFNLEVBQUUsVUFBVSxHQUFHLGtCQUFrQixFQUFFLEdBQUcsTUFBTSxDQUFDO3dCQUVuRCxNQUFNLENBQUMsS0FBSyxDQUFDLGlEQUFpRCxVQUFVLEVBQUUsQ0FBQyxDQUFDO3dCQUU1RSxxRUFBcUU7d0JBQ3JFLE1BQU0sSUFBSSxHQUFHLEtBQUssQ0FBQyxXQUFXLEVBQUUsVUFBVSxDQUFDLENBQUM7d0JBQzVDLE1BQU0sSUFBSSxHQUFhLEVBQUUsQ0FBQzt3QkFDMUIsTUFBTSxPQUFPLEdBQWtDLEVBQUUsQ0FBQzt3QkFFbEQsSUFBSSxDQUFDOzRCQUNELDZDQUE2Qzs0QkFDN0MsSUFBSSxPQUFPLElBQUksQ0FBQyxRQUFRLEtBQUssVUFBVSxFQUFFLENBQUM7Z0NBQ3RDLElBQUksQ0FBQztvQ0FDRCwrQ0FBK0M7b0NBQy9DLE1BQU0sUUFBUSxHQUFJLElBQVksQ0FBQyxRQUFRLEVBQUUsQ0FBQztvQ0FDMUMsSUFBSSxLQUFLLEVBQUUsTUFBTSxDQUFDLEdBQUcsRUFBRSxLQUFLLENBQUMsSUFBSSxRQUFRLEVBQUUsQ0FBQzt3Q0FDeEMsc0VBQXNFO3dDQUN0RSxNQUFNLFFBQVEsR0FBRyxHQUFHLENBQUMsT0FBTyxDQUFDLEdBQUcsVUFBVSxHQUFHLEVBQUUsRUFBRSxDQUFDLENBQUM7d0NBQ25ELElBQUksQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLENBQUM7d0NBRXBCLGdEQUFnRDt3Q0FDaEQsSUFBSSxJQUFJLEdBQUcsU0FBUyxDQUFDO3dDQUNyQixJQUFJLEtBQUssSUFBSSxPQUFPLEtBQUssS0FBSyxRQUFRLElBQUksS0FBSyxDQUFDLElBQUksRUFBRSxDQUFDOzRDQUNuRCxJQUFJLEdBQUcsS0FBSyxDQUFDLElBQUksQ0FBQzt3Q0FDdEIsQ0FBQzt3Q0FFRCxPQUFPLENBQUMsSUFBSSxDQUFDLEVBQUUsR0FBRyxFQUFFLFFBQVEsRUFBRSxJQUFJLEVBQUUsQ0FBQyxDQUFDO29DQUMxQyxDQUFDO2dDQUNMLENBQUM7Z0NBQUMsT0FBTyxpQkFBaUIsRUFBRSxDQUFDO29DQUN6QixNQUFNLENBQUMsSUFBSSxDQUFDLDBDQUEwQyxFQUFFLGlCQUFpQixDQUFDLENBQUM7b0NBQzNFLHlDQUF5QztvQ0FDekMsT0FBTzt3Q0FDSCxPQUFPLEVBQUUsSUFBSTt3Q0FDYixVQUFVO3dDQUNWLElBQUksRUFBRSxFQUFFO3dDQUNSLE9BQU8sRUFBRSxFQUFFO3dDQUNYLEtBQUssRUFBRSxDQUFDO3dDQUNSLE9BQU8sRUFBRSw0Q0FBNEM7cUNBQ3hELENBQUM7Z0NBQ04sQ0FBQzs0QkFDTCxDQUFDO2lDQUFNLENBQUM7Z0NBQ0osa0RBQWtEO2dDQUNsRCxNQUFNLENBQUMsSUFBSSxDQUFDLG9FQUFvRSxDQUFDLENBQUM7Z0NBQ2xGLE9BQU87b0NBQ0gsT0FBTyxFQUFFLElBQUk7b0NBQ2IsVUFBVTtvQ0FDVixJQUFJLEVBQUUsRUFBRTtvQ0FDUixPQUFPLEVBQUUsRUFBRTtvQ0FDWCxLQUFLLEVBQUUsQ0FBQztvQ0FDUixPQUFPLEVBQUUsNEZBQTRGO2lDQUN4RyxDQUFDOzRCQUNOLENBQUM7NEJBRUQsT0FBTztnQ0FDSCxPQUFPLEVBQUUsSUFBSTtnQ0FDYixVQUFVO2dDQUNWLElBQUk7Z0NBQ0osT0FBTztnQ0FDUCxLQUFLLEVBQUUsSUFBSSxDQUFDLE1BQU07NkJBQ3JCLENBQUM7d0JBQ04sQ0FBQzt3QkFBQyxPQUFPLGFBQWEsRUFBRSxDQUFDOzRCQUNyQix1REFBdUQ7NEJBQ3ZELE1BQU0sQ0FBQyxJQUFJLENBQUMscURBQXFELFVBQVUsR0FBRyxFQUFFLGFBQWEsQ0FBQyxDQUFDOzRCQUMvRixPQUFPO2dDQUNILE9BQU8sRUFBRSxJQUFJO2dDQUNiLFVBQVU7Z0NBQ1YsSUFBSSxFQUFFLEVBQUU7Z0NBQ1IsT0FBTyxFQUFFLEVBQUU7Z0NBQ1gsS0FBSyxFQUFFLENBQUM7Z0NBQ1IsT0FBTyxFQUFFLHdDQUF3Qzs2QkFDcEQsQ0FBQzt3QkFDTixDQUFDO29CQUNMLENBQUM7b0JBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQzt3QkFDYixNQUFNLENBQUMsS0FBSyxDQUFDLHdCQUF3QixFQUFFLEtBQUssQ0FBQyxDQUFDO3dCQUM5QyxPQUFPOzRCQUNILE9BQU8sRUFBRSxLQUFLOzRCQUNkLE9BQU8sRUFBRSxLQUFLLFlBQVksS0FBSyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyx3QkFBd0I7eUJBQzdFLENBQUM7b0JBQ04sQ0FBQztnQkFDTCxDQUFDO2dCQUNELEtBQUssRUFBRSxJQUFJLENBQUMsS0FBSzthQUNwQjtTQUN5QjtLQUNqQyxDQUFBO0FBQ0wsQ0FBQyxDQUFDIn0=