354 lines
26 KiB
JavaScript
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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWVtb3J5LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL2xpYi90b29scy9tZW1vcnkudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLElBQUksTUFBTSxNQUFNLENBQUE7QUFJNUIsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLGdCQUFnQixDQUFBO0FBRzNDLE9BQU8sRUFBRSxLQUFLLEVBQUUsR0FBRyxFQUFFLEdBQUcsRUFBRSxNQUFNLFdBQVcsQ0FBQTtBQUUzQyxzQ0FBc0M7QUFDdEMsTUFBTSxjQUFjLEdBQUcsQ0FBQyxPQUFrQixFQUFVLEVBQUU7SUFDbEQsc0VBQXNFO0lBQ3RFLE9BQU8sSUFBSSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsR0FBRyxFQUFFLEVBQUUsYUFBYSxDQUFDLENBQUM7QUFDbkQsQ0FBQyxDQUFDO0FBRUYsNkNBQTZDO0FBQzdDLE1BQU0sa0JBQWtCLEdBQUcsZUFBZSxDQUFDO0FBZTNDLG1EQUFtRDtBQUNuRCxNQUFNLHNCQUFzQixHQUFHLENBQUMsS0FBYSxFQUFFLE1BQWtCLEVBQVUsRUFBRTtJQUN6RSxRQUFRLE1BQU0sRUFBRTtRQUNaLEtBQUssTUFBTTtZQUNQLElBQUk7Z0JBQ0EsK0NBQStDO2dCQUMvQyxJQUFJLENBQUMsS0FBSyxDQUFDLEtBQUssQ0FBQyxDQUFDO2dCQUNsQixPQUFPLEtBQUssQ0FBQzthQUNoQjtZQUFDLE9BQU8sS0FBSyxFQUFFO2dCQUNaLE1BQU0sSUFBSSxLQUFLLENBQUMsOEJBQThCLENBQUMsQ0FBQzthQUNuRDtRQUNMLEtBQUssUUFBUTtZQUNULDRDQUE0QztZQUM1QyxJQUFJO2dCQUNBLGtCQUFrQjtnQkFDbEIsTUFBTSxDQUFDLElBQUksQ0FBQyxLQUFLLEVBQUUsUUFBUSxDQUFDLENBQUM7Z0JBQzdCLE9BQU8sS0FBSyxDQUFDO2FBQ2hCO1lBQUMsT0FBTyxLQUFLLEVBQUU7Z0JBQ1osTUFBTSxJQUFJLEtBQUssQ0FBQyx1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
|