UNPKG

n8n

Version:

n8n Workflow Automation Tool

245 lines 9.83 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.InstanceAiMemoryService = void 0; const backend_common_1 = require("@n8n/backend-common"); const config_1 = require("@n8n/config"); const di_1 = require("@n8n/di"); const instance_ai_1 = require("@n8n/instance-ai"); const db_snapshot_storage_1 = require("./storage/db-snapshot-storage"); const not_found_error_1 = require("../../errors/response-errors/not-found.error"); const message_parser_1 = require("./message-parser"); const typeorm_composite_store_1 = require("./storage/typeorm-composite-store"); let InstanceAiMemoryService = class InstanceAiMemoryService { constructor(logger, globalConfig, compositeStore, dbSnapshotStorage) { this.logger = logger; this.compositeStore = compositeStore; this.dbSnapshotStorage = dbSnapshotStorage; this.instanceAiConfig = globalConfig.instanceAi; } async listThreads(userId, page = 0, perPage = 100) { const memory = this.createMemoryInstance(); const result = await memory.listThreads({ filter: { resourceId: userId }, perPage, page, orderBy: { field: 'updatedAt', direction: 'DESC' }, }); return { threads: result.threads.map((t) => this.toThreadInfo(t)), total: result.total, page: result.page, hasMore: result.hasMore, }; } async ensureThread(userId, threadId) { const memory = this.createMemoryInstance(); const existing = await memory.getThreadById({ threadId }); if (existing) { if (existing.resourceId !== userId) { throw new Error(`Thread ${threadId} is not owned by user ${userId}`); } return { thread: this.toThreadInfo(existing), created: false, }; } const now = new Date(); const created = await memory.saveThread({ thread: { id: threadId, resourceId: userId, title: '', createdAt: now, updatedAt: now, }, }); return { thread: this.toThreadInfo(created), created: true, }; } async getThreadMessages(userId, threadId, options) { const memory = this.createMemoryInstance(); let result; try { result = await memory.recall({ threadId, resourceId: userId, perPage: options?.limit ?? 50, page: options?.page ?? 0, }); } catch (error) { if (error instanceof Error && error.message.includes('No thread found')) { return { threadId, messages: [] }; } throw error; } return { threadId, messages: result.messages.map((m) => ({ id: m.id, role: m.role, content: typeof m.content === 'string' ? m.content : m.content, type: m.type, createdAt: m.createdAt.toISOString(), })), }; } async getRichMessages(userId, threadId, options) { const memory = this.createMemoryInstance(); let result; try { result = await memory.recall({ threadId, resourceId: userId, perPage: options?.limit ?? 50, page: options?.page ?? 0, }); } catch (error) { if (error instanceof Error && error.message.includes('No thread found')) { return { threadId, messages: [] }; } throw error; } let snapshots = await this.dbSnapshotStorage.getAll(threadId).catch((error) => { this.logger.warn('Failed to load agent tree snapshots', { threadId, error: error instanceof Error ? error.message : String(error), }); return []; }); if (options?.excludeRunIds?.length) { const excluded = new Set(options.excludeRunIds); snapshots = snapshots.filter((s) => !excluded.has(s.runId)); } const mastraMessages = result.messages.map((m) => ({ id: m.id, role: m.role, content: m.content, type: m.type, createdAt: m.createdAt, })); const messages = (0, message_parser_1.parseStoredMessages)(mastraMessages, snapshots); return { threadId, messages }; } async getLatestRunSnapshot(threadId, options) { return await this.dbSnapshotStorage.getLatest(threadId, options); } async validateThreadOwnership(userId, threadId) { return (await this.checkThreadOwnership(userId, threadId)) === 'owned'; } async checkThreadOwnership(userId, threadId) { const memory = this.createMemoryInstance(); const thread = await memory.getThreadById({ threadId }); if (!thread) return 'not_found'; return thread.resourceId === userId ? 'owned' : 'other_user'; } async deleteThread(threadId) { const memory = this.createMemoryInstance(); await memory.deleteThread(threadId); } async renameThread(threadId, title) { return await this.updateThread(threadId, { title }); } async updateThread(threadId, updates) { const memory = this.createMemoryInstance(); const updated = await (0, instance_ai_1.patchThread)(memory, { threadId, update: ({ metadata }) => { const patch = { metadata: { ...metadata, ...updates.metadata }, }; if (updates.title !== undefined) { patch.title = updates.title; patch.metadata.titleRefined = true; } return patch; }, }); if (!updated) { throw new not_found_error_1.NotFoundError(`Thread ${threadId} not found`); } return this.toThreadInfo(updated); } async getThreadMetadata(userId, threadId) { const memory = this.createMemoryInstance(); const thread = await memory.getThreadById({ threadId }); if (!thread || thread.resourceId !== userId) return undefined; return thread.metadata; } async cleanupExpiredThreads(onThreadDeleted) { const ttlDays = this.instanceAiConfig.threadTtlDays; if (!ttlDays || ttlDays <= 0) return 0; const memory = this.createMemoryInstance(); const cutoff = new Date(Date.now() - ttlDays * 24 * 60 * 60 * 1000); let deletedCount = 0; const perPage = 100; let hasMore = true; while (hasMore) { const result = await memory.listThreads({ perPage, page: 0 }); let deletedInPage = 0; for (const thread of result.threads) { if (thread.updatedAt < cutoff) { try { await onThreadDeleted?.(thread.id); await memory.deleteThread(thread.id); deletedCount++; deletedInPage++; } catch (error) { this.logger.warn('Failed to delete expired thread', { threadId: thread.id, error: error instanceof Error ? error.message : String(error), }); } } } hasMore = deletedInPage > 0 && result.hasMore; } if (deletedCount > 0) { this.logger.info(`Cleaned up ${deletedCount} expired conversation threads (TTL: ${ttlDays} days)`); } return deletedCount; } createMemoryInstance() { return (0, instance_ai_1.createMemory)({ storage: this.compositeStore, embedderModel: this.instanceAiConfig.embedderModel || undefined, lastMessages: this.instanceAiConfig.lastMessages, semanticRecallTopK: this.instanceAiConfig.semanticRecallTopK, }); } toThreadInfo(thread) { return { id: thread.id, title: thread.title, resourceId: thread.resourceId, createdAt: thread.createdAt.toISOString(), updatedAt: thread.updatedAt.toISOString(), metadata: thread.metadata, }; } }; exports.InstanceAiMemoryService = InstanceAiMemoryService; exports.InstanceAiMemoryService = InstanceAiMemoryService = __decorate([ (0, di_1.Service)(), __metadata("design:paramtypes", [backend_common_1.Logger, config_1.GlobalConfig, typeorm_composite_store_1.TypeORMCompositeStore, db_snapshot_storage_1.DbSnapshotStorage]) ], InstanceAiMemoryService); //# sourceMappingURL=instance-ai-memory.service.js.map