/** * Unit tests for the Mastra integration * Tests processors, wrapper, and factory functions */ import { describe, it, expect, beforeEach, vi, afterEach } from "vitest" import { addBody, errorResponse, jsonResponse, profileBody, requestJson, requestPath, } from "../v5-fetch" import { RequestContext, MASTRA_THREAD_ID_KEY, } from "@mastra/core/request-context" import { SupermemoryInputProcessor, SupermemoryOutputProcessor, createSupermemoryProcessor, createSupermemoryOutputProcessor, createSupermemoryProcessors, withSupermemory, } from "../../src/mastra" import type { ProcessInputArgs, ProcessOutputResultArgs, MessageList, MastraDBMessage, MastraMessageContentV2, Processor, } from "../../src/mastra" const TEST_CONFIG = { apiKey: "test-api-key", baseUrl: "https://api.supermemory.ai", namespace: "test-mastra-user", id: "test-conversation", } interface MockAgentConfig { id: string name?: string model?: string customProp?: string inputProcessors?: Processor[] outputProcessors?: Processor[] [key: string]: unknown } /** * Helper to create MastraMessageContentV2 from text */ function createMessageContent(text: string): MastraMessageContentV2 { return { format: 2, parts: [{ type: "text", text }], } } /** * Helper to create a MastraDBMessage */ function createMessage( role: "user" | "assistant" | "system", text: string, ): MastraDBMessage { return { id: `msg-${Date.now()}-${Math.random().toString(36).slice(2)}`, role, content: createMessageContent(text), createdAt: new Date(), } } const createMockMessageList = (): MessageList & { calls: { method: string; args: unknown[] }[] } => { const calls: { method: string; args: unknown[] }[] = [] return { calls, clearSystemMessages: vi.fn(), addSystem: vi.fn((content: string, _id?: string) => { calls.push({ method: "addSystem", args: [content, _id] }) }), addUser: vi.fn((content: string) => { calls.push({ method: "addUser", args: [content] }) }), addAssistant: vi.fn((content: string) => { calls.push({ method: "addAssistant", args: [content] }) }), } as unknown as MessageList & { calls: { method: string; args: unknown[] }[] } } const createMockProfileResponse = profileBody const createMockConversationResponse = () => addBody("mem-123") describe("SupermemoryInputProcessor", () => { let originalEnv: string | undefined let originalFetch: typeof globalThis.fetch let fetchMock: ReturnType beforeEach(() => { originalEnv = process.env.SUPERMEMORY_API_KEY process.env.SUPERMEMORY_API_KEY = TEST_CONFIG.apiKey originalFetch = globalThis.fetch fetchMock = vi.fn() globalThis.fetch = fetchMock as unknown as typeof fetch vi.clearAllMocks() }) afterEach(() => { if (originalEnv) { process.env.SUPERMEMORY_API_KEY = originalEnv } else { delete process.env.SUPERMEMORY_API_KEY } globalThis.fetch = originalFetch }) describe("constructor", () => { it("should create processor with required options", () => { const processor = new SupermemoryInputProcessor({ namespace: TEST_CONFIG.namespace, id: TEST_CONFIG.id, }) expect(processor.id).toBe("supermemory-input") expect(processor.name).toBe("Supermemory Memory Injection") }) it("should throw error if API key is not set", () => { delete process.env.SUPERMEMORY_API_KEY expect(() => { new SupermemoryInputProcessor({ namespace: TEST_CONFIG.namespace, id: TEST_CONFIG.id, }) }).toThrow("SUPERMEMORY_API_KEY is not set") }) it("should accept API key via options", () => { delete process.env.SUPERMEMORY_API_KEY const processor = new SupermemoryInputProcessor({ namespace: TEST_CONFIG.namespace, id: TEST_CONFIG.id, apiKey: "custom-key", }) expect(processor.id).toBe("supermemory-input") }) }) describe("processInput", () => { it("should inject memories into messageList", async () => { fetchMock.mockImplementation(async () => jsonResponse( createMockProfileResponse( ["User likes TypeScript"], ["Recent interest in AI"], ), ), ) const processor = new SupermemoryInputProcessor({ namespace: TEST_CONFIG.namespace, id: TEST_CONFIG.id, apiKey: TEST_CONFIG.apiKey, mode: "profile", }) const messageList = createMockMessageList() const messages: MastraDBMessage[] = [createMessage("user", "Hello")] const args: ProcessInputArgs = { messages, systemMessages: [], messageList, abort: vi.fn() as never, retryCount: 0, } await processor.processInput(args) expect(messageList.addSystem).toHaveBeenCalled() const systemCall = messageList.calls.find((c) => c.method === "addSystem") expect(systemCall).toBeDefined() expect(systemCall?.args[0]).toContain("TypeScript") expect(systemCall?.args[0]).toContain( '', ) expect(systemCall?.args[1]).toBe("supermemory") }) it("should use cached memories on second call with same message", async () => { fetchMock.mockImplementation(async () => jsonResponse(createMockProfileResponse(["Cached memory"])), ) const processor = new SupermemoryInputProcessor({ namespace: TEST_CONFIG.namespace, id: TEST_CONFIG.id, apiKey: TEST_CONFIG.apiKey, mode: "profile", }) const messages: MastraDBMessage[] = [createMessage("user", "Hello")] const args1: ProcessInputArgs = { messages, systemMessages: [], messageList: createMockMessageList(), abort: vi.fn() as never, retryCount: 0, } await processor.processInput(args1) expect(fetchMock).toHaveBeenCalledTimes(1) const args2: ProcessInputArgs = { messages, systemMessages: [], messageList: createMockMessageList(), abort: vi.fn() as never, retryCount: 0, } await processor.processInput(args2) expect(fetchMock).toHaveBeenCalledTimes(1) }) it("should refetch memories for different user message", async () => { let callCount = 0 fetchMock.mockImplementation(() => { callCount++ return Promise.resolve( jsonResponse( createMockProfileResponse([`Memory from call ${callCount}`]), ), ) }) const processor = new SupermemoryInputProcessor({ namespace: TEST_CONFIG.namespace, id: TEST_CONFIG.id, apiKey: TEST_CONFIG.apiKey, mode: "query", }) const args1: ProcessInputArgs = { messages: [createMessage("user", "First message")], systemMessages: [], messageList: createMockMessageList(), abort: vi.fn() as never, retryCount: 0, } await processor.processInput(args1) expect(fetchMock).toHaveBeenCalledTimes(1) const args2: ProcessInputArgs = { messages: [createMessage("user", "Different message")], systemMessages: [], messageList: createMockMessageList(), abort: vi.fn() as never, retryCount: 0, } await processor.processInput(args2) expect(fetchMock).toHaveBeenCalledTimes(2) }) it("should return messageList in query mode when no user message", async () => { const processor = new SupermemoryInputProcessor({ namespace: TEST_CONFIG.namespace, id: TEST_CONFIG.id, apiKey: TEST_CONFIG.apiKey, mode: "query", }) const messageList = createMockMessageList() const args: ProcessInputArgs = { messages: [], systemMessages: [], messageList, abort: vi.fn() as never, retryCount: 0, } const result = await processor.processInput(args) expect(result).toBe(messageList) expect(fetchMock).not.toHaveBeenCalled() expect(messageList.addSystem).not.toHaveBeenCalled() }) it("should handle API errors gracefully", async () => { fetchMock.mockImplementation(async () => errorResponse(500)) const processor = new SupermemoryInputProcessor({ namespace: TEST_CONFIG.namespace, id: TEST_CONFIG.id, apiKey: TEST_CONFIG.apiKey, mode: "profile", }) const messageList = createMockMessageList() const args: ProcessInputArgs = { messages: [createMessage("user", "Hello")], systemMessages: [], messageList, abort: vi.fn() as never, retryCount: 0, } const result = await processor.processInput(args) expect(result).toBe(messageList) expect(messageList.addSystem).not.toHaveBeenCalled() }) it("should use threadId from options", async () => { fetchMock.mockImplementation(async () => jsonResponse(createMockProfileResponse(["Memory"])), ) const processor = new SupermemoryInputProcessor({ namespace: TEST_CONFIG.namespace, id: "thread-123", apiKey: TEST_CONFIG.apiKey, mode: "profile", }) const args: ProcessInputArgs = { messages: [createMessage("user", "Hello")], systemMessages: [], messageList: createMockMessageList(), abort: vi.fn() as never, retryCount: 0, } await processor.processInput(args) expect(fetchMock).toHaveBeenCalled() }) it("should use threadId from requestContext when not in options", async () => { fetchMock.mockImplementation(async () => jsonResponse(createMockProfileResponse(["Memory"])), ) const processor = new SupermemoryInputProcessor({ namespace: TEST_CONFIG.namespace, id: TEST_CONFIG.id, apiKey: TEST_CONFIG.apiKey, mode: "profile", }) const requestContext = new RequestContext() requestContext.set(MASTRA_THREAD_ID_KEY, "ctx-thread-456") const args: ProcessInputArgs = { messages: [createMessage("user", "Hello")], systemMessages: [], messageList: createMockMessageList(), abort: vi.fn() as never, retryCount: 0, requestContext, } await processor.processInput(args) expect(fetchMock).toHaveBeenCalled() }) it("should handle messages with array content parts", async () => { fetchMock.mockImplementation(async () => jsonResponse(createMockProfileResponse(["Memory"])), ) const processor = new SupermemoryInputProcessor({ namespace: TEST_CONFIG.namespace, id: TEST_CONFIG.id, apiKey: TEST_CONFIG.apiKey, mode: "query", }) const messages: MastraDBMessage[] = [ { id: "msg-1", role: "user", content: { format: 2, parts: [ { type: "text", text: "Hello " }, { type: "text", text: "World" }, ], }, createdAt: new Date(), }, ] const messageList = createMockMessageList() const args: ProcessInputArgs = { messages, systemMessages: [], messageList, abort: vi.fn() as never, retryCount: 0, } await processor.processInput(args) expect(fetchMock).toHaveBeenCalled() }) }) }) describe("SupermemoryOutputProcessor", () => { let originalEnv: string | undefined let originalFetch: typeof globalThis.fetch let fetchMock: ReturnType beforeEach(() => { originalEnv = process.env.SUPERMEMORY_API_KEY process.env.SUPERMEMORY_API_KEY = TEST_CONFIG.apiKey originalFetch = globalThis.fetch fetchMock = vi.fn() globalThis.fetch = fetchMock as unknown as typeof fetch vi.clearAllMocks() }) afterEach(() => { if (originalEnv) { process.env.SUPERMEMORY_API_KEY = originalEnv } else { delete process.env.SUPERMEMORY_API_KEY } globalThis.fetch = originalFetch }) describe("constructor", () => { it("should create processor with required options", () => { const processor = new SupermemoryOutputProcessor({ namespace: TEST_CONFIG.namespace, id: TEST_CONFIG.id, }) expect(processor.id).toBe("supermemory-output") expect(processor.name).toBe("Supermemory Conversation Save") }) }) describe("processOutputResult", () => { it("should save conversation when addMemory is always", async () => { fetchMock.mockImplementation(async () => jsonResponse(createMockConversationResponse()), ) const processor = new SupermemoryOutputProcessor({ namespace: TEST_CONFIG.namespace, id: "conv-456", apiKey: TEST_CONFIG.apiKey, addMemory: "always", }) const messages: MastraDBMessage[] = [ createMessage("user", "Hello"), createMessage("assistant", "Hi there!"), ] const args: ProcessOutputResultArgs = { messages, messageList: createMockMessageList(), abort: vi.fn() as never, retryCount: 0, } await processor.processOutputResult(args) expect(fetchMock).toHaveBeenCalledTimes(1) const [url, init] = fetchMock.mock.calls[0] as [string, RequestInit] expect(requestPath(url)).toBe(`/ns/${TEST_CONFIG.namespace}/document`) expect(init.method).toBe("POST") expect(new Headers(init.headers).get("authorization")).toBe( `Bearer ${TEST_CONFIG.apiKey}`, ) const callBody = requestJson(fetchMock.mock.calls[0]?.[1]) expect(callBody.id).toBe("conv-456") expect(String(callBody.content).split("\n\n")).toHaveLength(2) expect(callBody.dreaming).toBe("instant") }) it("should not save conversation when addMemory is never", async () => { const processor = new SupermemoryOutputProcessor({ namespace: TEST_CONFIG.namespace, id: "conv-456", apiKey: TEST_CONFIG.apiKey, addMemory: "never", }) const args: ProcessOutputResultArgs = { messages: [ createMessage("user", "Hello"), createMessage("assistant", "Hi!"), ], messageList: createMockMessageList(), abort: vi.fn() as never, retryCount: 0, } await processor.processOutputResult(args) expect(fetchMock).not.toHaveBeenCalled() }) it("should use id from options for conversation save", async () => { fetchMock.mockImplementation(async () => jsonResponse(createMockConversationResponse()), ) const processor = new SupermemoryOutputProcessor({ namespace: TEST_CONFIG.namespace, id: "my-custom-id", apiKey: TEST_CONFIG.apiKey, addMemory: "always", }) const args: ProcessOutputResultArgs = { messages: [ createMessage("user", "Hello"), createMessage("assistant", "Hi!"), ], messageList: createMockMessageList(), abort: vi.fn() as never, retryCount: 0, } await processor.processOutputResult(args) expect(fetchMock).toHaveBeenCalledTimes(1) const callBody = requestJson(fetchMock.mock.calls[0]?.[1]) expect(callBody.id).toBe("my-custom-id") }) it("should use threadId from requestContext (takes precedence over id)", async () => { fetchMock.mockImplementation(async () => jsonResponse(createMockConversationResponse()), ) const processor = new SupermemoryOutputProcessor({ namespace: TEST_CONFIG.namespace, id: "fallback-custom-id", apiKey: TEST_CONFIG.apiKey, addMemory: "always", }) const requestContext = new RequestContext() requestContext.set(MASTRA_THREAD_ID_KEY, "ctx-thread-789") const args: ProcessOutputResultArgs = { messages: [ createMessage("user", "Hello"), createMessage("assistant", "Hi!"), ], messageList: createMockMessageList(), abort: vi.fn() as never, retryCount: 0, requestContext, } await processor.processOutputResult(args) expect(fetchMock).toHaveBeenCalledTimes(1) const callBody = requestJson(fetchMock.mock.calls[0]?.[1]) // RequestContext threadId takes precedence for per-request dynamic IDs expect(callBody.id).toBe("ctx-thread-789") }) it("should fall back to id when requestContext has no threadId", async () => { fetchMock.mockImplementation(async () => jsonResponse(createMockConversationResponse()), ) const processor = new SupermemoryOutputProcessor({ namespace: TEST_CONFIG.namespace, id: "fallback-custom-id", apiKey: TEST_CONFIG.apiKey, addMemory: "always", }) const args: ProcessOutputResultArgs = { messages: [ createMessage("user", "Hello"), createMessage("assistant", "Hi!"), ], messageList: createMockMessageList(), abort: vi.fn() as never, retryCount: 0, } await processor.processOutputResult(args) expect(fetchMock).toHaveBeenCalledTimes(1) const callBody = requestJson(fetchMock.mock.calls[0]?.[1]) // Falls back to id when no RequestContext threadId expect(callBody.id).toBe("fallback-custom-id") }) it("should skip system messages when saving", async () => { fetchMock.mockImplementation(async () => jsonResponse(createMockConversationResponse()), ) const processor = new SupermemoryOutputProcessor({ namespace: TEST_CONFIG.namespace, id: "conv-456", apiKey: TEST_CONFIG.apiKey, addMemory: "always", }) const messages: MastraDBMessage[] = [ createMessage("system", "You are a helpful assistant"), createMessage("user", "Hello"), createMessage("assistant", "Hi there!"), ] const args: ProcessOutputResultArgs = { messages, messageList: createMockMessageList(), abort: vi.fn() as never, retryCount: 0, } await processor.processOutputResult(args) const callBody = requestJson(fetchMock.mock.calls[0]?.[1]) expect(String(callBody.content).split("\n\n")).toHaveLength(2) expect(callBody.content).not.toContain("System:") }) it("should handle messages with array content parts", async () => { fetchMock.mockImplementation(async () => jsonResponse(createMockConversationResponse()), ) const processor = new SupermemoryOutputProcessor({ namespace: TEST_CONFIG.namespace, id: "conv-456", apiKey: TEST_CONFIG.apiKey, addMemory: "always", }) const messages: MastraDBMessage[] = [ { id: "msg-1", role: "user", content: { format: 2, parts: [ { type: "text", text: "Hello" }, { type: "text", text: " World" }, ], }, createdAt: new Date(), }, { id: "msg-2", role: "assistant", content: { format: 2, parts: [{ type: "text", text: "Hi!" }], }, createdAt: new Date(), }, ] const args: ProcessOutputResultArgs = { messages, messageList: createMockMessageList(), abort: vi.fn() as never, retryCount: 0, } await processor.processOutputResult(args) const callBody = requestJson(fetchMock.mock.calls[0]?.[1]) expect(String(callBody.content).split("\n\n")).toHaveLength(2) }) it("should handle save errors gracefully", async () => { fetchMock.mockImplementation(async () => errorResponse(500)) const processor = new SupermemoryOutputProcessor({ namespace: TEST_CONFIG.namespace, id: "conv-456", apiKey: TEST_CONFIG.apiKey, addMemory: "always", }) const args: ProcessOutputResultArgs = { messages: [ createMessage("user", "Hello"), createMessage("assistant", "Hi!"), ], messageList: createMockMessageList(), abort: vi.fn() as never, retryCount: 0, } // Should not throw await expect(processor.processOutputResult(args)).resolves.toBeDefined() }) it("should not save when no messages to save", async () => { const processor = new SupermemoryOutputProcessor({ namespace: TEST_CONFIG.namespace, id: "conv-456", apiKey: TEST_CONFIG.apiKey, addMemory: "always", }) const args: ProcessOutputResultArgs = { messages: [], messageList: createMockMessageList(), abort: vi.fn() as never, retryCount: 0, } await processor.processOutputResult(args) expect(fetchMock).not.toHaveBeenCalled() }) }) }) describe("Factory functions", () => { let originalEnv: string | undefined beforeEach(() => { originalEnv = process.env.SUPERMEMORY_API_KEY process.env.SUPERMEMORY_API_KEY = TEST_CONFIG.apiKey }) afterEach(() => { if (originalEnv) { process.env.SUPERMEMORY_API_KEY = originalEnv } else { delete process.env.SUPERMEMORY_API_KEY } }) describe("createSupermemoryProcessor", () => { it("should create input processor", () => { const processor = createSupermemoryProcessor({ namespace: TEST_CONFIG.namespace, id: TEST_CONFIG.id, }) expect(processor).toBeInstanceOf(SupermemoryInputProcessor) expect(processor.id).toBe("supermemory-input") }) it("should pass options to processor", () => { const processor = createSupermemoryProcessor({ namespace: TEST_CONFIG.namespace, id: TEST_CONFIG.id, apiKey: "custom-key", mode: "full", }) expect(processor).toBeInstanceOf(SupermemoryInputProcessor) }) }) describe("createSupermemoryOutputProcessor", () => { it("should create output processor", () => { const processor = createSupermemoryOutputProcessor({ namespace: TEST_CONFIG.namespace, id: TEST_CONFIG.id, }) expect(processor).toBeInstanceOf(SupermemoryOutputProcessor) expect(processor.id).toBe("supermemory-output") }) it("should pass options to processor", () => { const processor = createSupermemoryOutputProcessor({ namespace: TEST_CONFIG.namespace, id: "conv-123", apiKey: "custom-key", addMemory: "always", }) expect(processor).toBeInstanceOf(SupermemoryOutputProcessor) }) }) describe("createSupermemoryProcessors", () => { it("should create both input and output processors", () => { const { input, output } = createSupermemoryProcessors({ namespace: TEST_CONFIG.namespace, id: TEST_CONFIG.id, }) expect(input).toBeInstanceOf(SupermemoryInputProcessor) expect(output).toBeInstanceOf(SupermemoryOutputProcessor) }) it("should share options between processors", () => { const { input, output } = createSupermemoryProcessors({ namespace: TEST_CONFIG.namespace, id: "conv-123", apiKey: "custom-key", mode: "full", addMemory: "always", }) expect(input.id).toBe("supermemory-input") expect(output.id).toBe("supermemory-output") }) }) }) describe("withSupermemory", () => { let originalEnv: string | undefined beforeEach(() => { originalEnv = process.env.SUPERMEMORY_API_KEY process.env.SUPERMEMORY_API_KEY = TEST_CONFIG.apiKey }) afterEach(() => { if (originalEnv) { process.env.SUPERMEMORY_API_KEY = originalEnv } else { delete process.env.SUPERMEMORY_API_KEY } }) describe("API key validation", () => { it("should throw error if API key is not set", () => { delete process.env.SUPERMEMORY_API_KEY const config: MockAgentConfig = { id: "test-agent", name: "Test Agent" } expect(() => { withSupermemory(config, { namespace: TEST_CONFIG.namespace, id: TEST_CONFIG.id, }) }).toThrow("SUPERMEMORY_API_KEY is not set") }) it("should accept API key via options", () => { delete process.env.SUPERMEMORY_API_KEY const config: MockAgentConfig = { id: "test-agent", name: "Test Agent" } const enhanced = withSupermemory(config, { namespace: TEST_CONFIG.namespace, id: TEST_CONFIG.id, apiKey: "custom-key", }) expect(enhanced).toBeDefined() expect(enhanced.inputProcessors).toHaveLength(1) }) }) describe("processor injection", () => { it("should inject input and output processors", () => { const config: MockAgentConfig = { id: "test-agent", name: "Test Agent" } const enhanced = withSupermemory(config, { namespace: TEST_CONFIG.namespace, id: TEST_CONFIG.id, }) expect(enhanced.inputProcessors).toHaveLength(1) expect(enhanced.outputProcessors).toHaveLength(1) expect(enhanced.inputProcessors?.[0]?.id).toBe("supermemory-input") expect(enhanced.outputProcessors?.[0]?.id).toBe("supermemory-output") }) it("should preserve original config properties", () => { const config: MockAgentConfig = { id: "test-agent", name: "Test Agent", model: "gpt-4", customProp: "value", } const enhanced = withSupermemory(config, { namespace: TEST_CONFIG.namespace, id: TEST_CONFIG.id, }) expect(enhanced.id).toBe("test-agent") expect(enhanced.name).toBe("Test Agent") expect(enhanced.model).toBe("gpt-4") expect(enhanced.customProp).toBe("value") }) it("should prepend input processor to existing processors", () => { const existingInputProcessor: Processor = { id: "existing-input", name: "Existing Input", } const config: MockAgentConfig = { id: "test-agent", name: "Test Agent", inputProcessors: [existingInputProcessor], } const enhanced = withSupermemory(config, { namespace: TEST_CONFIG.namespace, id: TEST_CONFIG.id, }) expect(enhanced.inputProcessors).toHaveLength(2) expect(enhanced.inputProcessors?.[0]?.id).toBe("supermemory-input") expect(enhanced.inputProcessors?.[1]?.id).toBe("existing-input") }) it("should append output processor to existing processors", () => { const existingOutputProcessor: Processor = { id: "existing-output", name: "Existing Output", } const config: MockAgentConfig = { id: "test-agent", name: "Test Agent", outputProcessors: [existingOutputProcessor], } const enhanced = withSupermemory(config, { namespace: TEST_CONFIG.namespace, id: TEST_CONFIG.id, }) expect(enhanced.outputProcessors).toHaveLength(2) expect(enhanced.outputProcessors?.[0]?.id).toBe("existing-output") expect(enhanced.outputProcessors?.[1]?.id).toBe("supermemory-output") }) it("should handle configs with both existing input and output processors", () => { const existingInput: Processor = { id: "existing-input" } const existingOutput: Processor = { id: "existing-output" } const config: MockAgentConfig = { id: "test-agent", name: "Test Agent", inputProcessors: [existingInput], outputProcessors: [existingOutput], } const enhanced = withSupermemory(config, { namespace: TEST_CONFIG.namespace, id: TEST_CONFIG.id, }) expect(enhanced.inputProcessors).toHaveLength(2) expect(enhanced.outputProcessors).toHaveLength(2) expect(enhanced.inputProcessors?.[0]?.id).toBe("supermemory-input") expect(enhanced.inputProcessors?.[1]?.id).toBe("existing-input") expect(enhanced.outputProcessors?.[0]?.id).toBe("existing-output") expect(enhanced.outputProcessors?.[1]?.id).toBe("supermemory-output") }) }) describe("options passthrough", () => { it("should pass options to processors", () => { const config: MockAgentConfig = { id: "test-agent", name: "Test Agent" } const enhanced = withSupermemory(config, { namespace: TEST_CONFIG.namespace, id: "conv-123", mode: "full", addMemory: "always", verbose: true, }) expect(enhanced.inputProcessors).toHaveLength(1) expect(enhanced.outputProcessors).toHaveLength(1) }) }) })