From 8d4a1fce6a23b2fcbd00de266d91659cb8e9eefd Mon Sep 17 00:00:00 2001 From: Oleg Ivaniv Date: Tue, 5 May 2026 10:58:47 +0200 Subject: [PATCH] refactor(instance-ai): decouple builder memory compaction --- .../builder-memory-compaction.test.ts | 47 +++++++++++------- .../builder-memory-compaction.ts | 49 +++++++++++++++---- 2 files changed, 70 insertions(+), 26 deletions(-) diff --git a/packages/@n8n/instance-ai/src/tools/orchestration/__tests__/builder-memory-compaction.test.ts b/packages/@n8n/instance-ai/src/tools/orchestration/__tests__/builder-memory-compaction.test.ts index f29f25a74e0..314cf7c8d07 100644 --- a/packages/@n8n/instance-ai/src/tools/orchestration/__tests__/builder-memory-compaction.test.ts +++ b/packages/@n8n/instance-ai/src/tools/orchestration/__tests__/builder-memory-compaction.test.ts @@ -1,11 +1,24 @@ -import type { MastraDBMessage } from '@mastra/core/agent'; -import type { MastraCompositeStore } from '@mastra/core/storage'; -import type { Workspace } from '@mastra/core/workspace'; - import { BuilderSandboxSessionRegistry } from '../../../runtime/builder-sandbox-session-registry'; import { compactBuilderMemoryThread } from '../builder-memory-compaction'; -function makeMessage(id: string, text: string): MastraDBMessage { +type CompactionInput = Parameters[0]; + +interface TestBuilderMemoryMessage { + id: string; + role: string; + threadId: string; + resourceId: string; + createdAt: Date; + type?: string; + content: { + format: number; + parts: Array<{ type: string; text: string }>; + content: string; + metadata?: Record; + }; +} + +function makeMessage(id: string, text: string): TestBuilderMemoryMessage { return { id, role: 'assistant', @@ -20,19 +33,17 @@ function makeMessage(id: string, text: string): MastraDBMessage { }; } -function makeStorage(memoryStore: unknown): MastraCompositeStore { +function makeStorage(memoryStore: unknown): CompactionInput['context']['storage'] { return { getStore: jest.fn(async (storeName: string) => { await Promise.resolve(); return storeName === 'memory' ? memoryStore : undefined; }), - } as unknown as MastraCompositeStore; + }; } -type CompactionInput = Parameters[0]; - function makeCompactionInput( - storage: MastraCompositeStore, + storage: CompactionInput['context']['storage'], overrides: Partial = {}, ): CompactionInput { return { @@ -85,10 +96,12 @@ describe('compactBuilderMemoryThread', () => { deleteMessages: jest.fn(async () => { await Promise.resolve(); }), - saveMessages: jest.fn(async ({ messages: saved }: { messages: MastraDBMessage[] }) => { - await Promise.resolve(); - return { messages: saved }; - }), + saveMessages: jest.fn( + async ({ messages: saved }: { messages: TestBuilderMemoryMessage[] }) => { + await Promise.resolve(); + return { messages: saved }; + }, + ), }; const result = await compactBuilderMemoryThread(makeCompactionInput(makeStorage(memoryStore))); @@ -123,7 +136,7 @@ describe('compactBuilderMemoryThread', () => { builderThreadId: 'builder-thread-1', builderResourceId: 'user-1:workflow-builder', builderWorkspace: { - workspace: {} as Workspace, + workspace: {} as never, cleanup, }, root: '/home/daytona/workspace', @@ -143,7 +156,7 @@ describe('compactBuilderMemoryThread', () => { deleteMessages: jest.fn(async () => { await Promise.resolve(); }), - saveMessages: jest.fn(async ({ messages }: { messages: MastraDBMessage[] }) => { + saveMessages: jest.fn(async ({ messages }: { messages: TestBuilderMemoryMessage[] }) => { await Promise.resolve(); return { messages }; }), @@ -178,7 +191,7 @@ describe('compactBuilderMemoryThread', () => { await Promise.resolve(); storedMessages = storedMessages.filter((message) => !messageIds.includes(message.id)); }), - saveMessages: jest.fn(async ({ messages }: { messages: MastraDBMessage[] }) => { + saveMessages: jest.fn(async ({ messages }: { messages: TestBuilderMemoryMessage[] }) => { await Promise.resolve(); storedMessages.push(...messages); return { messages }; diff --git a/packages/@n8n/instance-ai/src/tools/orchestration/builder-memory-compaction.ts b/packages/@n8n/instance-ai/src/tools/orchestration/builder-memory-compaction.ts index 8495540eb40..487d6b7634a 100644 --- a/packages/@n8n/instance-ai/src/tools/orchestration/builder-memory-compaction.ts +++ b/packages/@n8n/instance-ai/src/tools/orchestration/builder-memory-compaction.ts @@ -1,8 +1,5 @@ -import type { MastraDBMessage } from '@mastra/core/agent'; -import type { MastraCompositeStore, MemoryStorage } from '@mastra/core/storage'; import { randomUUID } from 'node:crypto'; -import type { OrchestrationContext } from '../../types'; import type { WorkflowBuildOutcome } from '../../workflow-loop'; const BUILDER_MEMORY_SUMMARY_TYPE = 'builder-memory-summary'; @@ -12,8 +9,19 @@ interface BuilderMemoryBinding { thread: string; } +interface BuilderMemoryStorageProvider { + getStore(storeName: string): Promise | unknown; +} + +interface BuilderMemoryCompactionContext { + storage: BuilderMemoryStorageProvider; + threadId: string; + runId: string; + messageGroupId?: string; +} + interface BuilderMemoryCompactionInput { - context: Pick; + context: BuilderMemoryCompactionContext; binding: BuilderMemoryBinding; sessionId?: string; workflowId?: string; @@ -38,10 +46,33 @@ export interface BuilderMemoryCompactionResult { compactedTokenEstimate: number; } +interface BuilderMemoryMessage { + id: string; + role: string; + threadId: string; + resourceId: string; + createdAt: Date; + type?: string; + content: unknown; +} + +interface BuilderMemoryListResult { + messages: BuilderMemoryMessage[]; + total?: number; + page?: number; + perPage?: number | false; + hasMore?: boolean; +} + interface BuilderMemoryStore { - listMessages: MemoryStorage['listMessages']; - saveMessages: MemoryStorage['saveMessages']; - deleteMessages: MemoryStorage['deleteMessages']; + listMessages: (args: { + threadId: string; + resourceId: string; + perPage: false; + orderBy: { field: 'createdAt'; direction: 'ASC' }; + }) => Promise; + saveMessages: (args: { messages: BuilderMemoryMessage[] }) => Promise; + deleteMessages: (messageIds: string[]) => Promise; } function estimateTokens(value: string): number { @@ -68,7 +99,7 @@ function hasBuilderMemoryStore(value: unknown): value is BuilderMemoryStore { } async function getBuilderMemoryStore( - storage: MastraCompositeStore, + storage: BuilderMemoryStorageProvider, ): Promise { const store = await storage.getStore('memory'); return hasBuilderMemoryStore(store) ? store : undefined; @@ -154,7 +185,7 @@ function buildSummaryContent(input: BuilderMemoryCompactionInput): string { function buildSummaryMessage( input: BuilderMemoryCompactionInput, content: string, -): MastraDBMessage { +): BuilderMemoryMessage { return { id: `${BUILDER_MEMORY_SUMMARY_TYPE}-${randomUUID()}`, role: 'assistant',