From 5a08a150715cfa087d0f8adef2fb04bb52525b6b Mon Sep 17 00:00:00 2001 From: abhinav7x94 Date: Sun, 16 Aug 2026 08:58:16 +0530 Subject: [PATCH] fix(tools): await VoltAgent conversation persistence --- packages/tools/src/voltagent/hooks.test.ts | 113 +++++++++++++++++++++ packages/tools/src/voltagent/hooks.ts | 6 +- packages/tools/src/voltagent/middleware.ts | 2 +- 3 files changed, 115 insertions(+), 6 deletions(-) create mode 100644 packages/tools/src/voltagent/hooks.test.ts diff --git a/packages/tools/src/voltagent/hooks.test.ts b/packages/tools/src/voltagent/hooks.test.ts new file mode 100644 index 000000000..619b4086b --- /dev/null +++ b/packages/tools/src/voltagent/hooks.test.ts @@ -0,0 +1,113 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest" +import { createSupermemoryHooks } from "./hooks" +import type { HookEndArgs } from "./types" + +const TEST_ARGS: HookEndArgs = { + agent: { name: "test-agent" }, + context: { + input: { + messages: [{ role: "user", content: "Remember this" }], + }, + }, + output: "I will remember that.", +} + +const createDeferredFetch = () => { + let resolveFetch: ((response: Response) => void) | undefined + const fetchPromise = new Promise((resolve) => { + resolveFetch = resolve + }) + const fetchMock = vi.fn(() => fetchPromise) + + return { + fetchMock, + resolve: (response: Response) => resolveFetch?.(response), + } +} + +describe("VoltAgent onEnd", () => { + let originalFetch: typeof globalThis.fetch + + beforeEach(() => { + originalFetch = globalThis.fetch + }) + + afterEach(() => { + globalThis.fetch = originalFetch + vi.restoreAllMocks() + }) + + it("waits for conversation persistence to finish", async () => { + const deferredFetch = createDeferredFetch() + globalThis.fetch = deferredFetch.fetchMock as unknown as typeof fetch + const hooks = createSupermemoryHooks("test-user", { + apiKey: "test-api-key", + baseUrl: "https://example.test", + customId: "test-conversation", + }) + let settled = false + + const onEndPromise = Promise.resolve(hooks.onEnd?.(TEST_ARGS)).finally( + () => { + settled = true + }, + ) + + expect(deferredFetch.fetchMock).toHaveBeenCalledOnce() + await Promise.resolve() + expect(settled).toBe(false) + + deferredFetch.resolve( + new Response( + JSON.stringify({ + id: "memory-1", + conversationId: "test-conversation", + status: "queued", + }), + { status: 200 }, + ), + ) + await onEndPromise + + expect(settled).toBe(true) + }) + + it("waits for a failed save, logs it, and resolves without throwing", async () => { + const deferredFetch = createDeferredFetch() + globalThis.fetch = deferredFetch.fetchMock as unknown as typeof fetch + vi.spyOn(console, "log").mockImplementation(() => {}) + const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {}) + const hooks = createSupermemoryHooks("test-user", { + apiKey: "test-api-key", + baseUrl: "https://example.test", + customId: "test-conversation", + verbose: true, + }) + let settled = false + + const onEndPromise = Promise.resolve(hooks.onEnd?.(TEST_ARGS)).finally( + () => { + settled = true + }, + ) + + expect(deferredFetch.fetchMock).toHaveBeenCalledOnce() + await Promise.resolve() + expect(settled).toBe(false) + + deferredFetch.resolve( + new Response("Server error", { + status: 500, + statusText: "Internal Server Error", + }), + ) + await expect(onEndPromise).resolves.toBeUndefined() + + expect(errorSpy).toHaveBeenCalledWith( + "[supermemory] Error saving conversation", + expect.stringContaining( + "Failed to add conversation: 500 Internal Server Error. Server error", + ), + ) + }) +}) diff --git a/packages/tools/src/voltagent/hooks.ts b/packages/tools/src/voltagent/hooks.ts index 87c788315..027ca0021 100644 --- a/packages/tools/src/voltagent/hooks.ts +++ b/packages/tools/src/voltagent/hooks.ts @@ -129,11 +129,7 @@ export function createSupermemoryHooks( return } - saveConversation(messages, ctx).catch((error) => { - ctx.logger.error("Background conversation save failed", { - error: error instanceof Error ? error.message : "Unknown error", - }) - }) + await saveConversation(messages, ctx) } catch (error) { ctx.logger.error("Error in onEnd", { error: error instanceof Error ? error.message : "Unknown error", diff --git a/packages/tools/src/voltagent/middleware.ts b/packages/tools/src/voltagent/middleware.ts index bf7717265..85b903648 100644 --- a/packages/tools/src/voltagent/middleware.ts +++ b/packages/tools/src/voltagent/middleware.ts @@ -448,7 +448,7 @@ const convertToConversationMessages = ( } /** - * Saves conversation to Supermemory (fire-and-forget). + * Saves conversation to Supermemory. */ export const saveConversation = async ( messages: VoltAgentMessage[],