n8n
Version:
n8n Workflow Automation Tool
245 lines • 9.83 kB
JavaScript
;
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