diff --git a/packages/tools/package.json b/packages/tools/package.json
index c4a39da4..d56a7dd1 100644
--- a/packages/tools/package.json
+++ b/packages/tools/package.json
@@ -1,11 +1,11 @@
{
"name": "@supermemory/tools",
"type": "module",
- "version": "1.4.4",
+ "version": "1.3.62",
"description": "Memory tools for AI SDK and OpenAI function calling with supermemory",
"scripts": {
"build": "tsdown",
- "dev": "tsdown --watch --ignore-watch .turbo",
+ "dev": "tsdown --watch",
"check-types": "tsc --noEmit",
"test": "vitest --testTimeout 100000",
"test:watch": "vitest --watch --testTimeout 100000"
@@ -14,31 +14,22 @@
"@ai-sdk/anthropic": "^2.0.25",
"@ai-sdk/openai": "^2.0.23",
"ai": "^5.0.29",
- "lru-cache": "^11.2.6",
"openai": "^4.104.0",
"supermemory": "^3.0.0-alpha.26",
"zod": "^4.1.5"
},
"devDependencies": {
"@ai-sdk/provider": "^3.0.0",
- "@anthropic-ai/sdk": "^0.65.0",
- "@voltagent/core": "^2.6.12",
- "@mastra/core": "^1.0.0",
"@total-typescript/tsconfig": "^1.0.4",
"@types/bun": "^1.2.21",
"dotenv": "^16.6.1",
"tsdown": "^0.14.2",
"typescript": "^5.9.2",
- "vitest": "^3.2.4"
+ "vitest": "^3.2.4",
+ "@anthropic-ai/sdk": "^0.65.0"
},
"peerDependencies": {
- "@ai-sdk/provider": "^2.0.0 || ^3.0.0",
- "@voltagent/core": "^2.6.12"
- },
- "peerDependenciesMeta": {
- "@voltagent/core": {
- "optional": true
- }
+ "@ai-sdk/provider": "^2.0.0 || ^3.0.0"
},
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -50,9 +41,7 @@
".": "./dist/index.js",
"./ai-sdk": "./dist/ai-sdk.js",
"./claude-memory": "./dist/claude-memory.js",
- "./mastra": "./dist/mastra.js",
"./openai": "./dist/openai/index.js",
- "./voltagent": "./dist/voltagent/index.js",
"./package.json": "./package.json"
},
"repository": {
diff --git a/packages/tools/src/vercel/index.ts b/packages/tools/src/vercel/index.ts
index b56b76a3..0eedc753 100644
--- a/packages/tools/src/vercel/index.ts
+++ b/packages/tools/src/vercel/index.ts
@@ -10,46 +10,14 @@ import {
extractAssistantResponseText,
saveMemoryAfterResponse,
} from "./middleware"
-import type { PromptTemplate, MemoryPromptData } from "./memory-prompt"
interface WrapVercelLanguageModelOptions {
- /** Optional conversation ID to group messages for contextual memory generation */
conversationId?: string
- /** Enable detailed logging of memory search and injection */
verbose?: boolean
- /**
- * Memory retrieval mode:
- * - "profile": Retrieves user profile memories (static + dynamic) without query filtering
- * - "query": Searches memories based on semantic similarity to the user's message
- * - "full": Combines both profile and query-based results
- */
mode?: "profile" | "query" | "full"
- /**
- * Memory persistence mode:
- * - "always": Automatically save conversations as memories
- * - "never": Only retrieve memories, don't store new ones
- */
addMemory?: "always" | "never"
- /** Supermemory API key (falls back to SUPERMEMORY_API_KEY env var) */
apiKey?: string
- /** Custom Supermemory API base URL */
baseUrl?: string
- /**
- * Custom function to format memory data into the system prompt.
- * If not provided, uses the default "User Supermemories:" format.
- *
- * @example
- * ```typescript
- * promptTemplate: (data) => `
- *
- * Here is some information about your past conversations:
- * ${data.userMemories}
- * ${data.generalSearchMemories}
- *
- * `.trim()
- * ```
- */
- promptTemplate?: PromptTemplate
}
/**
@@ -116,121 +84,103 @@ const wrapVercelLanguageModel = (
mode: options?.mode ?? "profile",
addMemory: options?.addMemory ?? "never",
baseUrl: options?.baseUrl,
- promptTemplate: options?.promptTemplate,
})
- // Proxy keeps prototype/getter fields (e.g. provider, modelId) that `{ ...model }` drops.
- return new Proxy(model, {
- get(target, prop, receiver) {
- if (prop === "doGenerate") {
- return async (params: LanguageModelCallOptions) => {
- try {
- const transformedParams = await transformParamsWithMemory(
- params,
- ctx,
- )
+ const wrappedModel = {
+ ...model,
- // biome-ignore lint/suspicious/noExplicitAny: Union type compatibility between V2 and V3
- const result = await target.doGenerate(transformedParams as any)
+ doGenerate: async (params: LanguageModelCallOptions) => {
+ try {
+ const transformedParams = await transformParamsWithMemory(params, ctx)
+ // biome-ignore lint/suspicious/noExplicitAny: Union type compatibility between V2 and V3
+ const result = await model.doGenerate(transformedParams as any)
+
+ const userMessage = getLastUserMessage(params)
+ if (ctx.addMemory === "always" && userMessage && userMessage.trim()) {
+ const assistantResponseText = extractAssistantResponseText(
+ result.content as unknown[],
+ )
+ saveMemoryAfterResponse(
+ ctx.client,
+ ctx.containerTag,
+ ctx.conversationId,
+ assistantResponseText,
+ params,
+ ctx.logger,
+ ctx.apiKey,
+ ctx.normalizedBaseUrl,
+ )
+ }
+
+ return result
+ } catch (error) {
+ ctx.logger.error("Error generating response", {
+ error: error instanceof Error ? error.message : "Unknown error",
+ })
+ throw error
+ }
+ },
+
+ doStream: async (params: LanguageModelCallOptions) => {
+ let generatedText = ""
+
+ try {
+ const transformedParams = await transformParamsWithMemory(params, ctx)
+
+ const { stream, ...rest } = await model.doStream(
+ // biome-ignore lint/suspicious/noExplicitAny: Union type compatibility between V2 and V3
+ transformedParams as any,
+ )
+
+ const transformStream = new TransformStream<
+ LanguageModelStreamPart,
+ LanguageModelStreamPart
+ >({
+ transform(chunk, controller) {
+ if (chunk.type === "text-delta") {
+ generatedText += chunk.delta
+ }
+ controller.enqueue(chunk)
+ },
+ flush: async () => {
const userMessage = getLastUserMessage(params)
if (
ctx.addMemory === "always" &&
userMessage &&
userMessage.trim()
) {
- const assistantResponseText = extractAssistantResponseText(
- result.content as unknown[],
- )
saveMemoryAfterResponse(
ctx.client,
ctx.containerTag,
ctx.conversationId,
- assistantResponseText,
+ generatedText,
params,
ctx.logger,
ctx.apiKey,
ctx.normalizedBaseUrl,
)
}
+ },
+ })
- return result
- } catch (error) {
- ctx.logger.error("Error generating response", {
- error: error instanceof Error ? error.message : "Unknown error",
- })
- throw error
- }
+ return {
+ stream: stream.pipeThrough(transformStream),
+ ...rest,
}
+ } catch (error) {
+ ctx.logger.error("Error streaming response", {
+ error: error instanceof Error ? error.message : "Unknown error",
+ })
+ throw error
}
-
- if (prop === "doStream") {
- return async (params: LanguageModelCallOptions) => {
- let generatedText = ""
-
- try {
- const transformedParams = await transformParamsWithMemory(
- params,
- ctx,
- )
-
- const { stream, ...rest } = await target.doStream(
- // biome-ignore lint/suspicious/noExplicitAny: Union type compatibility between V2 and V3
- transformedParams as any,
- )
-
- const transformStream = new TransformStream<
- LanguageModelStreamPart,
- LanguageModelStreamPart
- >({
- transform(chunk, controller) {
- if (chunk.type === "text-delta") {
- generatedText += chunk.delta
- }
- controller.enqueue(chunk)
- },
- flush: async () => {
- const userMessage = getLastUserMessage(params)
- if (
- ctx.addMemory === "always" &&
- userMessage &&
- userMessage.trim()
- ) {
- saveMemoryAfterResponse(
- ctx.client,
- ctx.containerTag,
- ctx.conversationId,
- generatedText,
- params,
- ctx.logger,
- ctx.apiKey,
- ctx.normalizedBaseUrl,
- )
- }
- },
- })
-
- return {
- stream: stream.pipeThrough(transformStream),
- ...rest,
- }
- } catch (error) {
- ctx.logger.error("Error streaming response", {
- error: error instanceof Error ? error.message : "Unknown error",
- })
- throw error
- }
- }
- }
-
- return Reflect.get(target, prop, receiver)
},
- }) as T
+ } as T
+
+ return wrappedModel
}
export {
wrapVercelLanguageModel as withSupermemory,
type WrapVercelLanguageModelOptions as WithSupermemoryOptions,
- type PromptTemplate,
- type MemoryPromptData,
}
diff --git a/packages/tools/src/vercel/memory-prompt.ts b/packages/tools/src/vercel/memory-prompt.ts
index 0f928aa2..1a9a05dd 100644
--- a/packages/tools/src/vercel/memory-prompt.ts
+++ b/packages/tools/src/vercel/memory-prompt.ts
@@ -1,70 +1,144 @@
-// Re-export shared types and functions
-export {
- type MemoryPromptData,
- type PromptTemplate,
- defaultPromptTemplate,
- normalizeBaseUrl,
- buildMemoriesText,
- type BuildMemoriesTextOptions,
-} from "../shared"
+import { deduplicateMemories } from "../shared"
+import type { Logger } from "./logger"
+import {
+ type LanguageModelCallOptions,
+ convertProfileToMarkdown,
+ type ProfileStructure,
+} from "./util"
-import type { Logger, MemoryPromptData } from "../shared"
-import type { LanguageModelCallOptions } from "./util"
-
-/**
- * Extracts the query text from params based on mode.
- * For "profile" mode, returns empty string (no query needed).
- * For "query" or "full" mode, extracts the last user message text.
- *
- * @param params - The language model call options
- * @param mode - The memory retrieval mode
- * @returns The query text for memory search
- */
-export const extractQueryText = (
- params: LanguageModelCallOptions,
- mode: "profile" | "query" | "full",
-): string => {
- if (mode === "profile") {
- return ""
- }
-
- const userMessage = params.prompt
- .slice()
- .reverse()
- .find((prompt: { role: string }) => prompt.role === "user")
-
- const content = userMessage?.content
- if (!content) return ""
-
- if (typeof content === "string") {
- return content
- }
-
- // biome-ignore lint/suspicious/noExplicitAny: Union type compatibility between V2 and V3
- return (content as any[])
- .filter((part) => part.type === "text")
- .map((part) => part.text || "")
- .join(" ")
+export const normalizeBaseUrl = (url?: string): string => {
+ const defaultUrl = "https://api.supermemory.ai"
+ if (!url) return defaultUrl
+ return url.endsWith("/") ? url.slice(0, -1) : url
}
-/**
- * Injects memories string into params by appending to existing system prompt
- * or creating a new one. Pure function - does not mutate the original params.
- *
- * @param params - The language model call options
- * @param memories - The formatted memories string to inject
- * @param logger - Logger for debug output
- * @returns New params with memories injected into the system prompt
- */
-export const injectMemoriesIntoParams = (
+const supermemoryProfileSearch = async (
+ containerTag: string,
+ queryText: string,
+ baseUrl: string,
+): Promise => {
+ const payload = queryText
+ ? JSON.stringify({
+ q: queryText,
+ containerTag: containerTag,
+ })
+ : JSON.stringify({
+ containerTag: containerTag,
+ })
+
+ try {
+ const response = await fetch(`${baseUrl}/v4/profile`, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${process.env.SUPERMEMORY_API_KEY}`,
+ },
+ body: payload,
+ })
+
+ if (!response.ok) {
+ const errorText = await response.text().catch(() => "Unknown error")
+ throw new Error(
+ `Supermemory profile search failed: ${response.status} ${response.statusText}. ${errorText}`,
+ )
+ }
+
+ return await response.json()
+ } catch (error) {
+ if (error instanceof Error) {
+ throw error
+ }
+ throw new Error(`Supermemory API request failed: ${error}`)
+ }
+}
+
+export const addSystemPrompt = async (
params: LanguageModelCallOptions,
- memories: string,
+ containerTag: string,
logger: Logger,
-): LanguageModelCallOptions => {
+ mode: "profile" | "query" | "full",
+ baseUrl = "https://api.supermemory.ai",
+): Promise => {
const systemPromptExists = params.prompt.some(
(prompt) => prompt.role === "system",
)
+ const queryText =
+ mode !== "profile"
+ ? params.prompt
+ .slice()
+ .reverse()
+ .find((prompt) => prompt.role === "user")
+ ?.content?.filter((content) => content.type === "text")
+ ?.map((content) => (content.type === "text" ? content.text : ""))
+ ?.join(" ") || ""
+ : ""
+
+ const memoriesResponse = await supermemoryProfileSearch(
+ containerTag,
+ queryText,
+ baseUrl,
+ )
+
+ const memoryCountStatic = memoriesResponse.profile.static?.length || 0
+ const memoryCountDynamic = memoriesResponse.profile.dynamic?.length || 0
+
+ logger.info("Memory search completed", {
+ containerTag,
+ memoryCountStatic,
+ memoryCountDynamic,
+ queryText:
+ queryText.substring(0, 100) + (queryText.length > 100 ? "..." : ""),
+ mode,
+ })
+
+ const deduplicated = deduplicateMemories({
+ static: memoriesResponse.profile.static,
+ dynamic: memoriesResponse.profile.dynamic,
+ searchResults: memoriesResponse.searchResults?.results,
+ })
+
+ logger.debug("Memory deduplication completed", {
+ static: {
+ original: memoryCountStatic,
+ deduplicated: deduplicated.static.length,
+ },
+ dynamic: {
+ original: memoryCountDynamic,
+ deduplicated: deduplicated.dynamic.length,
+ },
+ searchResults: {
+ original: memoriesResponse.searchResults.results.length,
+ deduplicated: deduplicated.searchResults?.length,
+ },
+ })
+
+ const profileData =
+ mode !== "query"
+ ? convertProfileToMarkdown({
+ profile: {
+ static: deduplicated.static,
+ dynamic: deduplicated.dynamic,
+ },
+ searchResults: { results: [] },
+ })
+ : ""
+ const searchResultsMemories =
+ mode !== "profile"
+ ? `Search results for user's recent message: \n${deduplicated.searchResults
+ .map((memory) => `- ${memory}`)
+ .join("\n")}`
+ : ""
+
+ const memories =
+ `User Supermemories: \n${profileData}\n${searchResultsMemories}`.trim()
+ if (memories) {
+ logger.debug("Memory content preview", {
+ content: memories,
+ fullLength: memories.length,
+ })
+ }
+
if (systemPromptExists) {
logger.debug("Added memories to existing system prompt")
// biome-ignore lint/suspicious/noExplicitAny: Union type compatibility between V2 and V3 prompt types
@@ -86,35 +160,3 @@ export const injectMemoriesIntoParams = (
] as any
return { ...params, prompt: newPrompt } as LanguageModelCallOptions
}
-
-/**
- * Adds memories to the system prompt by fetching from API and injecting.
- * This is the original combined function, now implemented via helpers.
- *
- * @deprecated Prefer using buildMemoriesText + injectMemoriesIntoParams for caching support
- */
-export const addSystemPrompt = async (
- params: LanguageModelCallOptions,
- containerTag: string,
- logger: Logger,
- mode: "profile" | "query" | "full",
- baseUrl: string,
- apiKey: string,
- promptTemplate?: (data: MemoryPromptData) => string,
-): Promise => {
- const { buildMemoriesText } = await import("../shared")
-
- const queryText = extractQueryText(params, mode)
-
- const memories = await buildMemoriesText({
- containerTag,
- queryText,
- mode,
- baseUrl,
- apiKey,
- logger,
- promptTemplate,
- })
-
- return injectMemoriesIntoParams(params, memories, logger)
-}
diff --git a/packages/tools/src/vercel/middleware.ts b/packages/tools/src/vercel/middleware.ts
index 8c31b86f..a2dd77ee 100644
--- a/packages/tools/src/vercel/middleware.ts
+++ b/packages/tools/src/vercel/middleware.ts
@@ -3,23 +3,17 @@ import {
addConversation,
type ConversationMessage,
} from "../conversations-client"
-import {
- createLogger,
- normalizeBaseUrl,
- MemoryCache,
- buildMemoriesText,
- type Logger,
- type PromptTemplate,
- type MemoryMode,
-} from "../shared"
+import { createLogger, type Logger } from "./logger"
import {
type LanguageModelCallOptions,
+ type LanguageModelStreamPart,
+ type OutputContentItem,
getLastUserMessage,
filterOutSupermemories,
} from "./util"
-import { extractQueryText, injectMemoriesIntoParams } from "./memory-prompt"
+import { addSystemPrompt, normalizeBaseUrl } from "./memory-prompt"
-const getConversationContent = (params: LanguageModelCallOptions) => {
+export const getConversationContent = (params: LanguageModelCallOptions) => {
return params.prompt
.filter((msg) => msg.role !== "system" && msg.role !== "tool")
.map((msg) => {
@@ -38,31 +32,31 @@ const getConversationContent = (params: LanguageModelCallOptions) => {
.join("\n\n")
}
-const convertToConversationMessages = (
+export const convertToConversationMessages = (
params: LanguageModelCallOptions,
assistantResponseText: string,
): ConversationMessage[] => {
const messages: ConversationMessage[] = []
for (const msg of params.prompt) {
- if (msg.role === "system") {
- continue
- }
-
if (typeof msg.content === "string") {
- if (msg.content) {
+ const filteredContent = filterOutSupermemories(msg.content)
+ if (filteredContent) {
messages.push({
- role: msg.role as "user" | "assistant" | "tool",
- content: msg.content,
+ role: msg.role as "user" | "assistant" | "system" | "tool",
+ content: filteredContent,
})
}
} else {
const contentParts = msg.content
.map((c) => {
- if (c.type === "text" && c.text) {
- return {
- type: "text" as const,
- text: c.text,
+ if (c.type === "text") {
+ const filteredText = filterOutSupermemories(c.text)
+ if (filteredText) {
+ return {
+ type: "text" as const,
+ text: filteredText,
+ }
}
}
if (
@@ -81,7 +75,7 @@ const convertToConversationMessages = (
if (contentParts.length > 0) {
messages.push({
- role: msg.role as "user" | "assistant" | "tool",
+ role: msg.role as "user" | "assistant" | "system" | "tool",
content: contentParts,
})
}
@@ -139,7 +133,7 @@ export const saveMemoryAfterResponse = async (
? `${getConversationContent(params)} \n\n Assistant: ${assistantResponseText}`
: `User: ${userMessage} \n\n Assistant: ${assistantResponseText}`
- const response = await client.add({
+ const response = await client.memories.add({
content,
containerTags: [containerTag],
customId,
@@ -159,52 +153,25 @@ export const saveMemoryAfterResponse = async (
}
}
-/**
- * Configuration options for the Supermemory middleware.
- */
-interface SupermemoryMiddlewareOptions {
- /** Container tag/identifier for memory search (e.g., user ID, project ID) */
+export interface SupermemoryMiddlewareOptions {
containerTag: string
- /** Supermemory API key */
apiKey: string
- /** Optional conversation ID to group messages for contextual memory generation */
conversationId?: string
- /** Enable detailed logging of memory search and injection */
verbose?: boolean
- /**
- * Memory retrieval mode:
- * - "profile": Retrieves user profile memories (static + dynamic) without query filtering
- * - "query": Searches memories based on semantic similarity to the user's message
- * - "full": Combines both profile and query-based results
- */
- mode?: MemoryMode
- /**
- * Memory persistence mode:
- * - "always": Automatically save conversations as memories
- * - "never": Only retrieve memories, don't store new ones
- */
+ mode?: "profile" | "query" | "full"
addMemory?: "always" | "never"
- /** Custom Supermemory API base URL */
baseUrl?: string
- /** Custom function to format memory data into the system prompt */
- promptTemplate?: PromptTemplate
}
-interface SupermemoryMiddlewareContext {
+export interface SupermemoryMiddlewareContext {
client: Supermemory
logger: Logger
containerTag: string
conversationId?: string
- mode: MemoryMode
+ mode: "profile" | "query" | "full"
addMemory: "always" | "never"
normalizedBaseUrl: string
apiKey: string
- promptTemplate?: PromptTemplate
- /**
- * Per-turn memory cache. Stores the injected memories string for each
- * user turn (keyed by turnKey) to avoid redundant API calls during tool-call
- */
- memoryCache: MemoryCache
}
export const createSupermemoryContext = (
@@ -218,7 +185,6 @@ export const createSupermemoryContext = (
mode = "profile",
addMemory = "never",
baseUrl,
- promptTemplate,
} = options
const logger = createLogger(verbose)
@@ -240,35 +206,9 @@ export const createSupermemoryContext = (
addMemory,
normalizedBaseUrl,
apiKey,
- promptTemplate,
- memoryCache: new MemoryCache(),
}
}
-/**
- * Generates a cache key for the current turn based on context and user message.
- * Uses the shared MemoryCache.makeTurnKey implementation.
- */
-const makeTurnKey = (
- ctx: SupermemoryMiddlewareContext,
- userMessage: string,
-): string => {
- return MemoryCache.makeTurnKey(
- ctx.containerTag,
- ctx.conversationId,
- ctx.mode,
- userMessage,
- )
-}
-
-/**
- * Checks if this is a new user turn (last message is from user)
- */
-const isNewUserTurn = (params: LanguageModelCallOptions): boolean => {
- const lastMessage = params.prompt.at(-1)
- return lastMessage?.role === "user"
-}
-
export const transformParamsWithMemory = async (
params: LanguageModelCallOptions,
ctx: SupermemoryMiddlewareContext,
@@ -282,42 +222,20 @@ export const transformParamsWithMemory = async (
}
}
- const turnKey = makeTurnKey(ctx, userMessage || "")
- const isNewTurn = isNewUserTurn(params)
-
- // Check if we can use cached memories
- const cachedMemories = ctx.memoryCache.get(turnKey)
- if (!isNewTurn && cachedMemories) {
- ctx.logger.debug("Using cached memories: ", {
- turnKey,
- })
- return injectMemoriesIntoParams(params, cachedMemories, ctx.logger)
- }
-
ctx.logger.info("Starting memory search", {
containerTag: ctx.containerTag,
conversationId: ctx.conversationId,
mode: ctx.mode,
- isNewTurn,
- cacheHit: false,
})
- const queryText = extractQueryText(params, ctx.mode)
-
- const memories = await buildMemoriesText({
- containerTag: ctx.containerTag,
- queryText,
- mode: ctx.mode,
- baseUrl: ctx.normalizedBaseUrl,
- apiKey: ctx.apiKey,
- logger: ctx.logger,
- promptTemplate: ctx.promptTemplate,
- })
-
- ctx.memoryCache.set(turnKey, memories)
- ctx.logger.debug("Cached memories for turn", { turnKey })
-
- return injectMemoriesIntoParams(params, memories, ctx.logger)
+ const transformedParams = await addSystemPrompt(
+ params,
+ ctx.containerTag,
+ ctx.logger,
+ ctx.mode,
+ ctx.normalizedBaseUrl,
+ )
+ return transformedParams
}
export const extractAssistantResponseText = (content: unknown[]): string => {
@@ -325,3 +243,47 @@ export const extractAssistantResponseText = (content: unknown[]): string => {
.map((item) => (item.type === "text" ? item.text || "" : ""))
.join("")
}
+
+export const createStreamTransform = (
+ ctx: SupermemoryMiddlewareContext,
+ params: LanguageModelCallOptions,
+): {
+ transform: TransformStream
+ getGeneratedText: () => string
+} => {
+ let generatedText = ""
+
+ const transform = new TransformStream<
+ LanguageModelStreamPart,
+ LanguageModelStreamPart
+ >({
+ transform(chunk, controller) {
+ if (chunk.type === "text-delta") {
+ generatedText += chunk.delta
+ }
+ controller.enqueue(chunk)
+ },
+ flush: async () => {
+ const userMessage = getLastUserMessage(params)
+ if (ctx.addMemory === "always" && userMessage && userMessage.trim()) {
+ saveMemoryAfterResponse(
+ ctx.client,
+ ctx.containerTag,
+ ctx.conversationId,
+ generatedText,
+ params,
+ ctx.logger,
+ ctx.apiKey,
+ ctx.normalizedBaseUrl,
+ )
+ }
+ },
+ })
+
+ return {
+ transform,
+ getGeneratedText: () => generatedText,
+ }
+}
+
+export { createLogger, type Logger, type OutputContentItem }
diff --git a/packages/tools/src/vercel/util.ts b/packages/tools/src/vercel/util.ts
index 49ab30c5..572f44a3 100644
--- a/packages/tools/src/vercel/util.ts
+++ b/packages/tools/src/vercel/util.ts
@@ -9,12 +9,6 @@ import type {
LanguageModelV3StreamPart,
} from "@ai-sdk/provider"
-// Re-export shared types for backward compatibility
-export type {
- ProfileStructure,
- ProfileMarkdownData,
-} from "../shared"
-
// Union types for dual SDK version support (V2 = SDK 5, V3 = SDK 6)
export type LanguageModel = LanguageModelV2 | LanguageModelV3
export type LanguageModelCallOptions =
@@ -27,6 +21,26 @@ export type LanguageModelStreamPart =
| LanguageModelV2StreamPart
| LanguageModelV3StreamPart
+export interface ProfileStructure {
+ profile: {
+ static?: Array<{ memory: string; metadata?: Record }>
+ dynamic?: Array<{ memory: string; metadata?: Record }>
+ }
+ searchResults: {
+ results: Array<{ memory: string; metadata?: Record }>
+ }
+}
+
+export interface ProfileMarkdownData {
+ profile: {
+ static?: string[]
+ dynamic?: string[]
+ }
+ searchResults: {
+ results: Array<{ memory: string }>
+ }
+}
+
export type OutputContentItem =
| { type: "text"; text: string }
| { type: "reasoning"; text: string }
@@ -44,33 +58,37 @@ export type OutputContentItem =
title: string
}
-// Re-export convertProfileToMarkdown from shared for backward compatibility
-export { convertProfileToMarkdown } from "../shared"
+/**
+ * Convert profile data to markdown format
+ * @param data Profile data with string arrays for static and dynamic memories
+ * @returns Markdown string with profile sections
+ */
+export function convertProfileToMarkdown(data: ProfileMarkdownData): string {
+ const sections: string[] = []
-export const getLastUserMessage = (
- params: LanguageModelCallOptions,
-): string | undefined => {
+ if (data.profile.static && data.profile.static.length > 0) {
+ sections.push("## Static Profile")
+ sections.push(data.profile.static.map((item) => `- ${item}`).join("\n"))
+ }
+
+ if (data.profile.dynamic && data.profile.dynamic.length > 0) {
+ sections.push("## Dynamic Profile")
+ sections.push(data.profile.dynamic.map((item) => `- ${item}`).join("\n"))
+ }
+
+ return sections.join("\n\n")
+}
+
+export const getLastUserMessage = (params: LanguageModelCallOptions) => {
const lastUserMessage = params.prompt
.slice()
.reverse()
.find((prompt: LanguageModelMessage) => prompt.role === "user")
-
- if (!lastUserMessage) {
- return undefined
- }
-
- const content = lastUserMessage.content
-
- // Handle string content directly
- if (typeof content === "string") {
- return content
- }
-
- // Handle array content - extract text parts
- return content
- .filter((part) => part.type === "text")
- .map((part) => (part as { type: "text"; text: string }).text)
+ const memories = lastUserMessage?.content
+ .filter((content) => content.type === "text")
+ .map((content) => (content as { type: "text"; text: string }).text)
.join(" ")
+ return memories
}
export const filterOutSupermemories = (content: string) => {