n8n
Version:
n8n Workflow Automation Tool
383 lines • 16.4 kB
JavaScript
"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