From 6bdc6ae11104e59acc67420db942fb635682c56c Mon Sep 17 00:00:00 2001 From: Aditya kumar singh <143548997+Adityakk9031@users.noreply.github.com> Date: Thu, 17 Sep 2026 17:05:29 +0530 Subject: [PATCH] fix(tools): handle trailing slashes and whitespace in normalizeBaseUrl and addConversation --- packages/tools/src/conversations-client.ts | 14 ++++++- packages/tools/src/shared/context.ts | 4 +- packages/tools/src/tools-shared.test.ts | 48 ++++++++++++++++++++++ 3 files changed, 63 insertions(+), 3 deletions(-) diff --git a/packages/tools/src/conversations-client.ts b/packages/tools/src/conversations-client.ts index 4a458e3a..b83e725d 100644 --- a/packages/tools/src/conversations-client.ts +++ b/packages/tools/src/conversations-client.ts @@ -6,6 +6,8 @@ * diffing and append detection on the backend. */ +import { normalizeBaseUrl } from "./shared/context" + export interface ConversationMessage { role: "user" | "assistant" | "system" | "tool" content: string | ContentPart[] @@ -52,6 +54,16 @@ export const toConversationImageUrl = ( : `data:${mediaType};base64,${trimmed}` } + if (typeof value === "object" && value !== null && "url" in value) { + const rawUrl = (value as { url: unknown }).url + if ( + typeof rawUrl === "string" || + (typeof URL !== "undefined" && rawUrl instanceof URL) + ) { + return toConversationImageUrl(rawUrl, mediaType) + } + } + const bytes = value instanceof Uint8Array ? value @@ -117,7 +129,7 @@ const CONVERSATION_REQUEST_TIMEOUT_MS = 30_000 export async function addConversation( params: AddConversationParams, ): Promise { - const baseUrl = params.baseUrl || "https://api.supermemory.ai" + const baseUrl = normalizeBaseUrl(params.baseUrl) const url = `${baseUrl}/v4/conversations` const response = await fetch(url, { diff --git a/packages/tools/src/shared/context.ts b/packages/tools/src/shared/context.ts index b4bf3eb1..ff3035a2 100644 --- a/packages/tools/src/shared/context.ts +++ b/packages/tools/src/shared/context.ts @@ -8,8 +8,8 @@ import Supermemory from "supermemory" */ export const normalizeBaseUrl = (url?: string): string => { const defaultUrl = "https://api.supermemory.ai" - if (!url) return defaultUrl - return url.endsWith("/") ? url.slice(0, -1) : url + const trimmed = url?.trim().replace(/\/+$/, "") + return trimmed || defaultUrl } /** diff --git a/packages/tools/src/tools-shared.test.ts b/packages/tools/src/tools-shared.test.ts index 3cc070ba..b7b15790 100644 --- a/packages/tools/src/tools-shared.test.ts +++ b/packages/tools/src/tools-shared.test.ts @@ -1,4 +1,6 @@ import { describe, expect, it } from "vitest" +import { toConversationImageUrl } from "./conversations-client" +import { normalizeBaseUrl } from "./shared/context" import { DEFAULT_VALUES, SEARCH_LIMIT_BOUNDS, @@ -130,3 +132,49 @@ describe("deduplicateMemoriesForMode", () => { expect(deduplicated.searchResults).toEqual([]) }) }) + +describe("normalizeBaseUrl", () => { + it("returns default URL when input is missing or empty", () => { + expect(normalizeBaseUrl()).toBe("https://api.supermemory.ai") + expect(normalizeBaseUrl("")).toBe("https://api.supermemory.ai") + expect(normalizeBaseUrl(" ")).toBe("https://api.supermemory.ai") + }) + + it("collapses single and multiple trailing slashes", () => { + expect(normalizeBaseUrl("http://localhost:6768/")).toBe( + "http://localhost:6768", + ) + expect(normalizeBaseUrl("http://localhost:6768///")).toBe( + "http://localhost:6768", + ) + expect(normalizeBaseUrl("https://api.supermemory.ai/")).toBe( + "https://api.supermemory.ai", + ) + }) + + it("trims whitespace around URLs", () => { + expect(normalizeBaseUrl(" http://localhost:6768/ ")).toBe( + "http://localhost:6768", + ) + }) +}) + +describe("toConversationImageUrl", () => { + it("handles string URLs and trims whitespace", () => { + expect(toConversationImageUrl("https://example.com/image.png")).toBe( + "https://example.com/image.png", + ) + }) + + it("handles object representations containing url", () => { + expect( + toConversationImageUrl({ url: "https://example.com/avatar.jpg" }), + ).toBe("https://example.com/avatar.jpg") + }) + + it("returns null for invalid or empty inputs", () => { + expect(toConversationImageUrl("")).toBeNull() + expect(toConversationImageUrl(null)).toBeNull() + expect(toConversationImageUrl(undefined)).toBeNull() + }) +})