n8n-nodes-zep-v3
Version:
Community node: Zep Memory (v3) - AI Agent Memory for n8n
203 lines • 8.37 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ZepMemory = void 0;
// Memory adapter compatible with Langchain's BaseChatMemory interface
// n8n AI Agent nodes expect: loadMemoryVariables, saveContext, clear
class ZepV3MemoryAdapter {
constructor(http, baseUrl, apiKey, threadId) {
this.http = http;
this.baseUrl = baseUrl;
this.apiKey = apiKey;
this.threadId = threadId;
this.memoryKey = 'chat_history';
this.returnMessages = true;
this.inputKey = 'input';
this.outputKey = 'output';
}
// Langchain interface: load memory variables
async loadMemoryVariables(_values) {
try {
console.log('[Zep Memory] loadMemoryVariables - API Key length:', this.apiKey?.length || 'UNDEFINED');
console.log('[Zep Memory] loadMemoryVariables - About to make request to:', `${this.baseUrl}/api/v2/threads/${this.threadId}/messages`);
// v3: GET /api/v2/threads/:threadId/messages
const res = await this.http.helpers.httpRequest({
baseURL: this.baseUrl,
url: `/api/v2/threads/${this.threadId}/messages`,
method: 'GET',
headers: {
'Authorization': `Api-Key ${this.apiKey}`,
'Content-Type': 'application/json',
},
});
console.log('[Zep Memory] loadMemoryVariables - Request successful');
// Validate response structure
if (!res || !Array.isArray(res.messages)) {
console.warn('[Zep Memory] Invalid response format from getMessages, returning empty array');
return { [this.memoryKey]: [] };
}
// Convert to Langchain message format
const messages = res.messages
.map((m) => {
if (!m.role || !m.content) {
console.warn('[Zep Memory] Message missing required fields (role/content), skipping');
return null;
}
// Return Langchain-compatible message object
return {
type: m.role === 'user' ? 'human' : 'ai',
content: m.content,
...(m.name ? { name: m.name } : {}),
};
})
.filter((m) => m !== null);
return { [this.memoryKey]: messages };
}
catch (error) {
console.error('[Zep Memory] Error loading memory:', error);
return { [this.memoryKey]: [] };
}
}
// Langchain interface: save conversation context
async saveContext(inputValues, outputValues) {
try {
const input = inputValues[this.inputKey];
const output = outputValues[this.outputKey];
if (!input && !output) {
console.warn('[Zep Memory] No input or output to save');
return;
}
const messages = [];
if (input) {
messages.push({ role: 'user', content: String(input) });
}
if (output) {
messages.push({ role: 'assistant', content: String(output) });
}
// v3: POST /api/v2/threads/:threadId/messages
const body = { messages };
await this.http.helpers.httpRequest({
baseURL: this.baseUrl,
url: `/api/v2/threads/${this.threadId}/messages`,
method: 'POST',
headers: {
'Authorization': `Api-Key ${this.apiKey}`,
'Content-Type': 'application/json',
},
body,
});
console.log(`[Zep Memory] Saved ${messages.length} message(s) to thread ${this.threadId}`);
}
catch (error) {
console.error('[Zep Memory] Error saving context:', error);
throw error;
}
}
// Langchain interface: clear memory
async clear() {
try {
// v3: DELETE /api/v2/threads/:threadId
await this.http.helpers.httpRequest({
baseURL: this.baseUrl,
url: `/api/v2/threads/${this.threadId}`,
method: 'DELETE',
headers: {
'Authorization': `Api-Key ${this.apiKey}`,
'Content-Type': 'application/json',
},
});
console.log(`[Zep Memory] Thread ${this.threadId} cleared successfully`);
}
catch (error) {
console.error('[Zep Memory] Error clearing memory:', error);
throw error;
}
}
}
class ZepMemory {
constructor() {
this.description = {
displayName: 'Zep Memory (v3)',
name: 'zepMemoryV3',
icon: 'file:zep-logo.png',
group: ['transform'],
version: 1,
subtitle: 'Threads & User Context',
description: 'Use Zep v3 Threads as chat memory',
defaults: { name: 'Zep Memory (v3)' },
// Sub-node: no data inputs, special AI output
inputs: [],
outputs: ['ai_memory'],
credentials: [{ name: 'zepApiV3', required: true }],
properties: [
{
displayName: 'Thread ID',
name: 'threadIdType',
type: 'options',
options: [
{
name: 'Connected Chat Trigger Node',
value: 'fromInput',
description: 'Looks for an input field called "sessionId" from a connected Chat Trigger',
},
{
name: 'Define below',
value: 'customKey',
description: 'Use an expression to reference data in previous nodes or enter static text',
},
],
default: 'fromInput',
},
{
displayName: 'Thread Key From Previous Node',
name: 'threadKey',
type: 'string',
default: '={{ $json.sessionId }}',
displayOptions: {
show: {
threadIdType: ['fromInput'],
},
},
},
{
displayName: 'Thread ID',
name: 'threadKey',
type: 'string',
default: '',
description: 'The thread ID to use to store the memory',
displayOptions: {
show: {
threadIdType: ['customKey'],
},
},
},
],
};
}
async supplyData(itemIndex) {
const credentials = await this.getCredentials('zepApiV3');
const baseUrl = credentials.baseUrl;
const apiKey = credentials.apiKey;
// Get threadId based on user selection
const threadIdType = this.getNodeParameter('threadIdType', itemIndex);
let threadId;
if (threadIdType === 'fromInput') {
const threadKey = this.getNodeParameter('threadKey', itemIndex, 'sessionId');
const inputData = this.getInputData(itemIndex);
threadId = String(inputData?.[0]?.json?.[threadKey] || '');
}
else {
threadId = this.getNodeParameter('threadKey', itemIndex);
}
console.log(`[Zep Memory] Initializing memory adapter for thread: ${threadId}`);
console.log(`[Zep Memory] Base URL: ${baseUrl}`);
console.log(`[Zep Memory] API Key present: ${apiKey ? 'YES (length: ' + apiKey.length + ')' : 'NO - MISSING!'}`);
// Expose an adapter instance on the special AI memory port
const adapter = new ZepV3MemoryAdapter(this, baseUrl, apiKey, threadId);
console.log(`[Zep Memory] Memory adapter ready`);
return {
response: adapter,
};
}
}
exports.ZepMemory = ZepMemory;
//# sourceMappingURL=ZepMemory.node.js.map