mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-10-01 02:01:40 +00:00
Merge 049a50ee0a into cfa6c7cb17
This commit is contained in:
commit
7ec597b7fe
2 changed files with 65 additions and 5 deletions
|
|
@ -21,7 +21,7 @@ vi.mock("supermemory", () => {
|
|||
}
|
||||
})
|
||||
|
||||
import { ClaudeMemoryTool } from "./claude-memory"
|
||||
import { ClaudeMemoryTool, pathToCustomId } from "./claude-memory"
|
||||
|
||||
const FILE_PATH = "/memories/notes.txt"
|
||||
// 5 distinct lines so an off-by-one at either end is observable.
|
||||
|
|
@ -210,3 +210,38 @@ describe("ClaudeMemoryTool str_replace replacement literalness", () => {
|
|||
expect(stored).toContain(`price is ${dollarSequence} today`)
|
||||
})
|
||||
})
|
||||
|
||||
describe("pathToCustomId", () => {
|
||||
it("keeps the legacy flattening for canonical single-segment paths", () => {
|
||||
// Backward compatibility: documents already stored under the legacy id
|
||||
// (no underscores, one dot, one slash) must keep resolving.
|
||||
expect(pathToCustomId("/memories/file.txt")).toBe("memories_file_txt")
|
||||
expect(pathToCustomId("/projects/spec.md")).toBe("projects_spec_md")
|
||||
// Shapes outside the canonical /dir/file.ext form are digested even
|
||||
// when they look flattenable.
|
||||
expect(pathToCustomId("notes.txt")).toBe("notes_txt_e39538e7")
|
||||
})
|
||||
|
||||
it("disambiguates paths that would collide under plain flattening", () => {
|
||||
// "notes.txt", "notes_txt", and "notes/txt" all flattened to the same
|
||||
// id, so one file could silently overwrite another (#1547).
|
||||
const a = pathToCustomId("/memories/notes.txt")
|
||||
const b = pathToCustomId("/memories/notes_txt")
|
||||
const c = pathToCustomId("/memories/notes/txt")
|
||||
|
||||
expect(new Set([a, b, c]).size).toBe(3)
|
||||
for (const id of [a, b, c]) {
|
||||
expect(id.startsWith("memories_notes_txt")).toBe(true)
|
||||
}
|
||||
})
|
||||
|
||||
it("is deterministic for the same input", () => {
|
||||
expect(pathToCustomId("/memories/my_notes/v1.txt")).toBe(
|
||||
pathToCustomId("/memories/my_notes/v1.txt"),
|
||||
)
|
||||
})
|
||||
|
||||
it("differs for distinct underscore paths sharing a flattening", () => {
|
||||
expect(pathToCustomId("/a_b/c.txt")).not.toBe(pathToCustomId("/a/b_c.txt"))
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { createHash } from "node:crypto"
|
||||
import Supermemory from "supermemory"
|
||||
import { deleteDocumentById, getContainerTags } from "./tools-shared"
|
||||
import type { SupermemoryToolsConfig } from "./types"
|
||||
|
|
@ -45,6 +46,33 @@ interface ClaudeFileDocument {
|
|||
metadata: ClaudeFileMetadata
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a memory file path to the customId used for document identity.
|
||||
*
|
||||
* Paths in the canonical single-segment shape (`/memories/file.txt`: no
|
||||
* underscores, exactly one dot, exactly one slash) keep the exact legacy id,
|
||||
* so documents already stored under it continue to resolve.
|
||||
*
|
||||
* Plain flattening is otherwise non-injective — `notes.txt`, `notes_txt`,
|
||||
* and `notes/txt` all collapse to the same id, so creating one file could
|
||||
* silently overwrite another (#1547). Every other shape therefore gets a
|
||||
* short digest of the original path appended, making distinct paths map to
|
||||
* distinct ids again.
|
||||
*/
|
||||
export function pathToCustomId(path: string): string {
|
||||
const stripped = path.replace(/^\//, "")
|
||||
const legacyCompatible =
|
||||
!stripped.includes("_") &&
|
||||
stripped.split(".").length === 2 &&
|
||||
stripped.split("/").length === 2
|
||||
if (legacyCompatible) {
|
||||
return stripped.replace(/\//g, "_").replace(/\./g, "_")
|
||||
}
|
||||
const flattened = stripped.replace(/\//g, "_").replace(/\./g, "_")
|
||||
const digest = createHash("sha256").update(stripped).digest("hex").slice(0, 8)
|
||||
return `${flattened}_${digest}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Claude Memory Tool - Client-side implementation
|
||||
* Maps Claude's memory tool commands to supermemory document operations
|
||||
|
|
@ -60,10 +88,7 @@ export class ClaudeMemoryTool {
|
|||
* Converts /memories/file.txt -> memories_file_txt
|
||||
*/
|
||||
private normalizePathToCustomId(path: string): string {
|
||||
return path
|
||||
.replace(/^\//, "") // Remove leading slash
|
||||
.replace(/\//g, "_") // Replace / with _
|
||||
.replace(/\./g, "_") // Replace . with _
|
||||
return pathToCustomId(path)
|
||||
}
|
||||
|
||||
constructor(apiKey: string, config?: ClaudeMemoryConfig) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue