UNPKG

n8n

Version:

n8n Workflow Automation Tool

383 lines 16.4 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.N8nMemory = void 0; const di_1 = require("@n8n/di"); const typeorm_1 = require("@n8n/typeorm"); const n8n_workflow_1 = require("n8n-workflow"); const agent_observation_cursor_entity_1 = require("../entities/agent-observation-cursor.entity"); const agent_observation_lock_entity_1 = require("../entities/agent-observation-lock.entity"); const agent_observation_entity_1 = require("../entities/agent-observation.entity"); const agent_thread_entity_1 = require("../entities/agent-thread.entity"); const agent_message_repository_1 = require("../repositories/agent-message.repository"); const agent_observation_cursor_repository_1 = require("../repositories/agent-observation-cursor.repository"); const agent_observation_lock_repository_1 = require("../repositories/agent-observation-lock.repository"); const agent_observation_repository_1 = require("../repositories/agent-observation.repository"); const agent_resource_repository_1 = require("../repositories/agent-resource.repository"); const agent_thread_repository_1 = require("../repositories/agent-thread.repository"); const WORKING_MEMORY_KEY = 'workingMemory'; let N8nMemory = class N8nMemory { constructor(threadRepository, messageRepository, resourceRepository, observationRepository, observationCursorRepository, observationLockRepository) { this.threadRepository = threadRepository; this.messageRepository = messageRepository; this.resourceRepository = resourceRepository; this.observationRepository = observationRepository; this.observationCursorRepository = observationCursorRepository; this.observationLockRepository = observationLockRepository; } async getThread(threadId) { const entity = await this.threadRepository.findOneBy({ id: threadId }); if (!entity) return null; return this.toThread(entity); } async saveThread(thread) { await this.ensureResource(thread.resourceId); const existing = await this.threadRepository.findOneBy({ id: thread.id }); if (existing) { if (thread.title !== undefined) existing.title = thread.title; if (thread.metadata !== undefined) { existing.metadata = thread.metadata ? JSON.stringify(thread.metadata) : null; } const saved = await this.threadRepository.save(existing); return this.toThread(saved); } const entity = this.threadRepository.create({ id: thread.id, resourceId: thread.resourceId, title: thread.title ?? null, metadata: thread.metadata ? JSON.stringify(thread.metadata) : null, }); const saved = await this.threadRepository.save(entity); return this.toThread(saved); } async ensureResource(resourceId) { const exists = await this.resourceRepository.existsBy({ id: resourceId }); if (!exists) { await this.resourceRepository.save(this.resourceRepository.create({ id: resourceId, metadata: null })); } } async deleteThread(threadId) { await this.threadRepository.manager.transaction(async (trx) => { const scope = { scopeKind: 'thread', scopeId: threadId }; await trx.delete(agent_observation_entity_1.AgentObservationEntity, scope); await trx.delete(agent_observation_cursor_entity_1.AgentObservationCursorEntity, scope); await trx.delete(agent_observation_lock_entity_1.AgentObservationLockEntity, scope); await trx.delete(agent_thread_entity_1.AgentThreadEntity, { id: threadId }); }); } async deleteThreadsByPrefix(threadIdPrefix) { const scopeId = (0, typeorm_1.Like)(`${threadIdPrefix}%`); await this.threadRepository.manager.transaction(async (trx) => { const scope = { scopeKind: 'thread', scopeId }; await trx.delete(agent_observation_entity_1.AgentObservationEntity, scope); await trx.delete(agent_observation_cursor_entity_1.AgentObservationCursorEntity, scope); await trx.delete(agent_observation_lock_entity_1.AgentObservationLockEntity, scope); await trx.delete(agent_thread_entity_1.AgentThreadEntity, { id: scopeId }); }); } async getMessages(threadId, opts) { const where = { threadId, ...(opts?.before && { createdAt: (0, typeorm_1.LessThan)(opts.before) }), ...(opts?.resourceId !== undefined && { resourceId: opts.resourceId }), }; const entities = await this.messageRepository.find({ where, order: { createdAt: opts?.limit !== undefined ? 'DESC' : 'ASC' }, ...(opts?.limit !== undefined && { take: opts.limit }), }); if (opts?.limit !== undefined) { entities.reverse(); } return entities.map((e) => { const msg = e.content; msg.id = e.id; return msg; }); } async saveMessages(args) { if (args.messages.length === 0) return; const now = new Date(); const entities = args.messages.map((dbMsg) => { const role = 'role' in dbMsg ? dbMsg.role : 'custom'; const type = 'type' in dbMsg ? dbMsg.type : null; return { id: dbMsg.id, threadId: args.threadId, resourceId: args.resourceId, role, type: type ?? null, content: dbMsg, createdAt: dbMsg.createdAt, updatedAt: now, }; }); await this.messageRepository.upsert(entities, ['id']); } async deleteMessages(messageIds) { if (messageIds.length === 0) return; await this.messageRepository.delete(messageIds); } async deleteMessagesByThread(threadId, resourceId) { await this.messageRepository.delete({ threadId, ...(resourceId !== undefined && { resourceId }), }); } async getWorkingMemory(params) { if (params.scope === 'resource') { const resource = await this.resourceRepository.findOneBy({ id: params.resourceId }); return this.extractWorkingMemory(resource?.metadata ?? null); } const thread = await this.threadRepository.findOneBy({ id: params.threadId }); return this.extractWorkingMemory(thread?.metadata ?? null); } async saveWorkingMemory(params, content) { if (params.scope === 'resource') { await this.upsertResourceMetadata(params.resourceId, content); } else { await this.upsertThreadMetadata(params.threadId, params.resourceId, content); } } async appendObservations(rows) { if (rows.length === 0) return []; const entities = rows.map((row) => this.observationRepository.create({ scopeKind: row.scopeKind, scopeId: row.scopeId, kind: row.kind, payload: row.payload, durationMs: row.durationMs, schemaVersion: row.schemaVersion, createdAt: row.createdAt, })); const saved = await this.observationRepository.save(entities); return saved.map((e) => this.toObservation(e)); } async getObservations(opts) { const baseWhere = { scopeKind: opts.scopeKind, scopeId: opts.scopeId, ...(opts.kindIs !== undefined && { kind: opts.kindIs }), ...(opts.schemaVersionAtMost !== undefined && { schemaVersion: (0, typeorm_1.LessThanOrEqual)(opts.schemaVersionAtMost), }), }; const where = opts.since ? [ { ...baseWhere, createdAt: (0, typeorm_1.MoreThan)(opts.since.sinceCreatedAt) }, { ...baseWhere, createdAt: (0, typeorm_1.Equal)(opts.since.sinceCreatedAt), id: (0, typeorm_1.MoreThan)(opts.since.sinceObservationId), }, ] : [baseWhere]; const entities = await this.observationRepository.find({ where, order: { createdAt: 'ASC', id: 'ASC' }, ...(opts.limit !== undefined && { take: opts.limit }), }); return entities.map((e) => this.toObservation(e)); } async getMessagesForScope(scopeKind, scopeId, opts) { if (scopeKind !== 'thread') { throw new n8n_workflow_1.UnexpectedError(`getMessagesForScope: scopeKind='${scopeKind}' is not supported in observational memory v1`); } const baseWhere = { threadId: scopeId }; const where = opts?.since ? [ { ...baseWhere, createdAt: (0, typeorm_1.MoreThan)(opts.since.sinceCreatedAt) }, { ...baseWhere, createdAt: (0, typeorm_1.Equal)(opts.since.sinceCreatedAt), id: (0, typeorm_1.MoreThan)(opts.since.sinceMessageId), }, ] : [baseWhere]; const entities = await this.messageRepository.find({ where, order: { createdAt: 'ASC', id: 'ASC' }, }); return entities.map((e) => { const msg = e.content; msg.id = e.id; return msg; }); } async deleteObservations(ids) { if (ids.length === 0) return; await this.observationRepository.delete({ id: (0, typeorm_1.In)(ids) }); } async getCursor(scopeKind, scopeId) { const entity = await this.observationCursorRepository.findOneBy({ scopeKind, scopeId }); if (!entity) return null; return { scopeKind: entity.scopeKind, scopeId: entity.scopeId, lastObservedMessageId: entity.lastObservedMessageId, lastObservedAt: entity.lastObservedAt, updatedAt: entity.updatedAt, }; } async setCursor(cursor) { await this.observationCursorRepository.upsert({ scopeKind: cursor.scopeKind, scopeId: cursor.scopeId, lastObservedMessageId: cursor.lastObservedMessageId, lastObservedAt: cursor.lastObservedAt, updatedAt: cursor.updatedAt, }, { conflictPaths: ['scopeKind', 'scopeId'], skipUpdateIfNoValuesChanged: false }); } async acquireObservationLock(scopeKind, scopeId, opts) { const now = new Date(); const heldUntil = new Date(now.getTime() + opts.ttlMs); const updateResult = await this.observationLockRepository .createQueryBuilder() .update(agent_observation_lock_entity_1.AgentObservationLockEntity) .set({ holderId: opts.holderId, heldUntil }) .where('"scopeKind" = :scopeKind') .andWhere('"scopeId" = :scopeId') .andWhere('("holderId" = :holderId OR "heldUntil" <= :now)') .setParameters({ scopeKind, scopeId, holderId: opts.holderId, now }) .execute(); if ((updateResult.affected ?? 0) > 0) { return { scopeKind, scopeId, holderId: opts.holderId, heldUntil }; } await this.observationLockRepository .createQueryBuilder() .insert() .into(agent_observation_lock_entity_1.AgentObservationLockEntity) .values({ scopeKind, scopeId, holderId: opts.holderId, heldUntil }) .orIgnore() .execute(); const claimed = await this.observationLockRepository.findOneBy({ scopeKind, scopeId, holderId: opts.holderId, }); if (!claimed) return null; return { scopeKind, scopeId, holderId: opts.holderId, heldUntil }; } async releaseObservationLock(handle) { await this.observationLockRepository.delete({ scopeKind: handle.scopeKind, scopeId: handle.scopeId, holderId: handle.holderId, }); } describe() { return { name: 'n8n', connectionParams: {}, constructorName: this.constructor.name }; } toObservation(entity) { return { id: entity.id, scopeKind: entity.scopeKind, scopeId: entity.scopeId, kind: entity.kind, payload: entity.payload, durationMs: entity.durationMs === null ? null : Number(entity.durationMs), schemaVersion: Number(entity.schemaVersion), createdAt: entity.createdAt, }; } toThread(entity) { let metadata; if (entity.metadata) { try { metadata = JSON.parse(entity.metadata); } catch { metadata = undefined; } } return { id: entity.id, resourceId: entity.resourceId, title: entity.title ?? undefined, metadata, createdAt: entity.createdAt, updatedAt: entity.updatedAt, }; } extractWorkingMemory(metadataJson) { if (!metadataJson) return null; try { const parsed = JSON.parse(metadataJson); const wm = parsed[WORKING_MEMORY_KEY]; return typeof wm === 'string' ? wm : null; } catch { return null; } } mergeWorkingMemory(existingJson, content) { let parsed = {}; if (existingJson) { try { parsed = JSON.parse(existingJson); } catch { } } parsed[WORKING_MEMORY_KEY] = content; return JSON.stringify(parsed); } async upsertResourceMetadata(resourceId, content) { const existing = await this.resourceRepository.findOneBy({ id: resourceId }); if (existing) { existing.metadata = this.mergeWorkingMemory(existing.metadata, content); await this.resourceRepository.save(existing); } else { const entity = this.resourceRepository.create({ id: resourceId, metadata: this.mergeWorkingMemory(null, content), }); await this.resourceRepository.save(entity); } } async upsertThreadMetadata(threadId, resourceId, content) { const existing = await this.threadRepository.findOneBy({ id: threadId }); if (existing) { existing.metadata = this.mergeWorkingMemory(existing.metadata, content); await this.threadRepository.save(existing); return; } await this.ensureResource(resourceId); await this.threadRepository.save(this.threadRepository.create({ id: threadId, resourceId, title: null, metadata: this.mergeWorkingMemory(null, content), })); } }; exports.N8nMemory = N8nMemory; exports.N8nMemory = N8nMemory = __decorate([ (0, di_1.Service)(), __metadata("design:paramtypes", [agent_thread_repository_1.AgentThreadRepository, agent_message_repository_1.AgentMessageRepository, agent_resource_repository_1.AgentResourceRepository, agent_observation_repository_1.AgentObservationRepository, agent_observation_cursor_repository_1.AgentObservationCursorRepository, agent_observation_lock_repository_1.AgentObservationLockRepository]) ], N8nMemory); //# sourceMappingURL=n8n-memory.js.map