Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 16 additions & 18 deletions packages/tools/src/ai-sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
TOOL_DESCRIPTIONS,
getContainerTags,
} from "./tools-shared"
import { forgetMemoryRequest } from "./shared/forget-memory"
import type { SupermemoryToolsConfig } from "./types"

// Export individual tool creators
Expand Down Expand Up @@ -191,23 +192,21 @@ export const documentListTool = (
.optional()
.default(DEFAULT_VALUES.limit)
.describe(PARAMETER_DESCRIPTIONS.limit),
offset: z.number().optional().describe(PARAMETER_DESCRIPTIONS.offset),
status: z.string().optional().describe(PARAMETER_DESCRIPTIONS.status),
page: z.number().optional().describe(PARAMETER_DESCRIPTIONS.page),
}),
execute: async ({ containerTag, limit, offset, status }) => {
execute: async ({ containerTag, limit, page }) => {
try {
const tag = containerTag || containerTags[0]

const response = await client.documents.list({
containerTags: [tag],
limit: limit || DEFAULT_VALUES.limit,
...(offset !== undefined && { offset }),
...(status && { status }),
...(page !== undefined && { page }),
})

return {
success: true,
documents: response.documents,
documents: response.memories,
pagination: response.pagination,
}
} catch (error) {
Expand Down Expand Up @@ -236,7 +235,7 @@ export const documentDeleteTool = (
}),
execute: async ({ documentId }) => {
try {
await client.documents.delete({ docId: documentId })
await client.documents.delete(documentId)

return {
success: true,
Expand Down Expand Up @@ -303,11 +302,6 @@ export const memoryForgetTool = (
apiKey: string,
config?: SupermemoryToolsConfig,
) => {
const client = new Supermemory({
apiKey,
...(config?.baseUrl ? { baseURL: config.baseUrl } : {}),
})

const containerTags = getContainerTags(config)

return tool({
Expand Down Expand Up @@ -335,12 +329,16 @@ export const memoryForgetTool = (

const tag = containerTag || containerTags[0]

await client.memories.forget({
containerTag: tag,
...(memoryId && { id: memoryId }),
...(memoryContent && { content: memoryContent }),
...(reason && { reason }),
})
await forgetMemoryRequest(
apiKey,
{
containerTag: tag as string,
...(memoryId && { id: memoryId }),
...(memoryContent && { content: memoryContent }),
...(reason && { reason }),
},
config?.baseUrl,
)

return {
success: true,
Expand Down
26 changes: 20 additions & 6 deletions packages/tools/src/claude-memory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,10 @@ export class ClaudeMemoryTool {
}
return await this.create(command.path, command.file_text)
case "str_replace":
if (!command.old_str || !command.new_str) {
// new_str may legitimately be "" (deleting text), so only reject
// when it is missing entirely. old_str must be non-empty — replacing
// the empty string would prepend instead of replacing.
if (!command.old_str || command.new_str === undefined) {
return {
success: false,
error: "old_str and new_str are required for str_replace command",
Expand All @@ -108,7 +111,11 @@ export class ClaudeMemoryTool {
command.new_str,
)
case "insert":
if (command.insert_line === undefined || !command.insert_text) {
// insert_text may be "" (inserting a blank line).
if (
command.insert_line === undefined ||
command.insert_text === undefined
) {
return {
success: false,
error:
Expand Down Expand Up @@ -487,9 +494,9 @@ export class ClaudeMemoryTool {
}
}

// Delete using the document ID
// Note: We'll need to implement this based on supermemory's delete API
// For now, we'll return a success message
const documentId =
readResult.document.documentId ?? this.normalizePathToCustomId(filePath)
await this.client.documents.delete(documentId)

return {
success: true,
Expand Down Expand Up @@ -544,7 +551,14 @@ export class ClaudeMemoryTool {
},
})

// Delete the old document (would need proper delete API)
// Remove the old document so the previous path stops showing up in
// listings and search. Skip when both paths normalize to the same
// customId — the add above already replaced the content.
const oldNormalizedId = this.normalizePathToCustomId(oldPath)
if (oldNormalizedId !== newNormalizedId) {
const oldDocumentId = readResult.document.documentId ?? oldNormalizedId
await this.client.documents.delete(oldDocumentId)
}

return {
success: true,
Expand Down
40 changes: 19 additions & 21 deletions packages/tools/src/openai/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
TOOL_DESCRIPTIONS,
getContainerTags,
} from "../tools-shared"
import { forgetMemoryRequest } from "../shared/forget-memory"
import type { SupermemoryToolsConfig } from "../types"

/**
Expand Down Expand Up @@ -36,7 +37,7 @@ export interface ProfileResult {

export interface DocumentListResult {
success: boolean
documents?: Awaited<ReturnType<Supermemory["documents"]["list"]>>["documents"]
documents?: Awaited<ReturnType<Supermemory["documents"]["list"]>>["memories"]
pagination?: Awaited<
ReturnType<Supermemory["documents"]["list"]>
>["pagination"]
Expand Down Expand Up @@ -139,13 +140,9 @@ export const memoryToolSchemas = {
description: PARAMETER_DESCRIPTIONS.limit,
default: DEFAULT_VALUES.limit,
},
offset: {
page: {
type: "number",
description: PARAMETER_DESCRIPTIONS.offset,
},
status: {
type: "string",
description: PARAMETER_DESCRIPTIONS.status,
description: PARAMETER_DESCRIPTIONS.page,
},
},
required: [],
Expand Down Expand Up @@ -359,27 +356,24 @@ export function createDocumentListFunction(
return async function documentList({
containerTag,
limit,
offset,
status,
page,
}: {
containerTag?: string
limit?: number
offset?: number
status?: string
page?: number
}): Promise<DocumentListResult> {
try {
const tag = containerTag || containerTags[0]

const response = await client.documents.list({
containerTags: [tag],
limit: limit || DEFAULT_VALUES.limit,
...(offset !== undefined && { offset }),
...(status && { status }),
...(page !== undefined && { page }),
})

return {
success: true,
documents: response.documents,
documents: response.memories,
pagination: response.pagination,
}
} catch (error) {
Expand Down Expand Up @@ -470,7 +464,7 @@ export function createMemoryForgetFunction(
apiKey: string,
config?: SupermemoryToolsConfig,
) {
const { client, containerTags } = createClient(apiKey, config)
const containerTags = getContainerTags(config)

return async function memoryForget({
containerTag,
Expand All @@ -493,12 +487,16 @@ export function createMemoryForgetFunction(

const tag = containerTag || containerTags[0]

await client.memories.forget({
containerTag: tag,
...(memoryId && { id: memoryId }),
...(memoryContent && { content: memoryContent }),
...(reason && { reason }),
})
await forgetMemoryRequest(
apiKey,
{
containerTag: tag as string,
...(memoryId && { id: memoryId }),
...(memoryContent && { content: memoryContent }),
...(reason && { reason }),
},
config?.baseUrl,
)

return {
success: true,
Expand Down
38 changes: 38 additions & 0 deletions packages/tools/src/shared/forget-memory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
const DEFAULT_BASE_URL = "https://api.supermemory.ai"

export interface ForgetMemoryParams {
containerTag: string
id?: string
content?: string
reason?: string
}

/**
* Marks a memory as forgotten via `DELETE /v4/memories`.
*
* The supermemory SDK version this package depends on (v3) has no
* `memories.forget` method, so the endpoint is called directly — the same
* pattern the middleware already uses for `/v4/profile` and
* `/v4/conversations`.
*/
export async function forgetMemoryRequest(
apiKey: string,
params: ForgetMemoryParams,
baseUrl: string = DEFAULT_BASE_URL,
): Promise<void> {
const response = await fetch(`${baseUrl}/v4/memories`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`,
},
body: JSON.stringify(params),
})

if (!response.ok) {
const errorText = await response.text().catch(() => "Unknown error")
throw new Error(
`Supermemory forget memory failed: ${response.status} ${response.statusText}. ${errorText}`,
)
Comment on lines +34 to +36

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rule 'Include any supporting data as the cause argument instead of inlining into the string' is violated here. The error is constructed by inlining the status code, status text, and error body directly into the error message string. Instead, the supporting data (status, statusText, errorText) should be passed as the cause option to new Error(). For example:

throw new Error('Supermemory forget memory failed', {
  cause: { status: response.status, statusText: response.statusText, body: errorText },
})
Suggested change
throw new Error(
`Supermemory forget memory failed: ${response.status} ${response.statusText}. ${errorText}`,
)
throw new Error('Supermemory forget memory failed', {
cause: { status: response.status, statusText: response.statusText, body: errorText },
})

Spotted by Graphite (based on custom rule: TypeScript style guide (Google))

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

}
}
Loading