mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-09-07 08:26:15 +00:00
feat(sdk-playground): reflect SDK-owned memory block in debug view
Update the playground to display the current deduplicated <supermemory> replacement block produced by the SDK middleware instead of a browser-side seen-facts delta. Add memory-dedupe helper and ignore local tsbuildinfo. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
cb557f404f
commit
0fe2dabcee
2 changed files with 88 additions and 13 deletions
|
|
@ -3,6 +3,7 @@ import {
|
|||
type MiddlewareRuntimeConfig,
|
||||
normalizeMiddlewareConfig,
|
||||
} from "./middleware-config"
|
||||
import { dedupeProfileForMode } from "./memory-dedupe"
|
||||
|
||||
export interface MemoryDebugEntry {
|
||||
type:
|
||||
|
|
@ -91,17 +92,6 @@ function summarizeProfile(profile: ContainerContext["profile"]) {
|
|||
}
|
||||
}
|
||||
|
||||
function selectProfileForMode(
|
||||
profile: ContainerContext["profile"],
|
||||
mode: "profile" | "query" | "full",
|
||||
): ContainerContext["profile"] {
|
||||
return {
|
||||
static: mode === "query" ? [] : profile.static,
|
||||
dynamic: mode === "query" ? [] : profile.dynamic,
|
||||
searchResults: mode === "profile" ? [] : profile.searchResults,
|
||||
}
|
||||
}
|
||||
|
||||
function buildContextPreview(
|
||||
profile: ContainerContext["profile"],
|
||||
mode: "profile" | "query" | "full",
|
||||
|
|
@ -182,7 +172,10 @@ export async function fetchContainerContext(
|
|||
if (!apiKey) throw new Error("Supermemory API key is required")
|
||||
|
||||
const client = getSupermemoryClient(apiKey)
|
||||
const profile = await fetchProfileContext(client, containerTag, query)
|
||||
const profile = dedupeProfileForMode(
|
||||
query ? "full" : "profile",
|
||||
await fetchProfileContext(client, containerTag, query),
|
||||
)
|
||||
|
||||
const docsResponse = await client.post<{
|
||||
documents?: unknown[]
|
||||
|
|
@ -246,7 +239,7 @@ export async function buildMiddlewareMemoryDebug(
|
|||
query,
|
||||
signal,
|
||||
)
|
||||
const selectedProfile = selectProfileForMode(profile, memoryMode)
|
||||
const selectedProfile = dedupeProfileForMode(memoryMode, profile)
|
||||
const summary = summarizeProfile(selectedProfile)
|
||||
|
||||
return [
|
||||
|
|
@ -276,6 +269,12 @@ export async function buildMiddlewareMemoryDebug(
|
|||
type: "context_preview",
|
||||
label: "Reconstructed context preview (not middleware capture)",
|
||||
preview: buildContextPreview(selectedProfile, memoryMode, query),
|
||||
detail: {
|
||||
totalFacts:
|
||||
summary.staticCount +
|
||||
summary.dynamicCount +
|
||||
summary.searchResultCount,
|
||||
},
|
||||
},
|
||||
config.addMemory === "always"
|
||||
? {
|
||||
|
|
|
|||
76
apps/sdk-playground/src/lib/memory-dedupe.ts
Normal file
76
apps/sdk-playground/src/lib/memory-dedupe.ts
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
import type { ContainerContext } from "./context-api"
|
||||
|
||||
type ProfileSlice = ContainerContext["profile"]
|
||||
|
||||
/** Normalize a fact for exact comparison within retrieved context. */
|
||||
export function normalizeFactKey(text: string): string {
|
||||
return text
|
||||
.trim()
|
||||
.replace(/^\[recent\]\s*/i, "")
|
||||
.replace(/^\[\d{4}-\d{2}-\d{2}\]\s*/, "")
|
||||
.trim()
|
||||
.replace(/\s+/g, " ")
|
||||
.toLowerCase()
|
||||
}
|
||||
|
||||
function memoryText(item: unknown): string {
|
||||
if (typeof item === "string") return item
|
||||
if (item && typeof item === "object") {
|
||||
const record = item as Record<string, unknown>
|
||||
if (typeof record.memory === "string") return record.memory
|
||||
if (typeof record.content === "string") return record.content
|
||||
if (typeof record.chunk === "string") return record.chunk
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
/**
|
||||
* Deduplicate static → dynamic → search (same priority as @supermemory/tools middleware).
|
||||
*/
|
||||
export function dedupeProfileForMode(
|
||||
mode: "profile" | "query" | "full",
|
||||
profile: ProfileSlice,
|
||||
): ProfileSlice {
|
||||
const injectsProfile = mode !== "query"
|
||||
const staticItems = injectsProfile ? profile.static : []
|
||||
const dynamicItems = injectsProfile ? profile.dynamic : []
|
||||
const searchItems = profile.searchResults
|
||||
|
||||
const seen = new Set<string>()
|
||||
const staticOut: unknown[] = []
|
||||
const dynamicOut: unknown[] = []
|
||||
const searchOut: unknown[] = []
|
||||
|
||||
for (const item of staticItems) {
|
||||
const text = memoryText(item).trim()
|
||||
if (!text) continue
|
||||
const key = normalizeFactKey(text)
|
||||
if (!key || seen.has(key)) continue
|
||||
seen.add(key)
|
||||
staticOut.push(item)
|
||||
}
|
||||
|
||||
for (const item of dynamicItems) {
|
||||
const text = memoryText(item).trim()
|
||||
if (!text) continue
|
||||
const key = normalizeFactKey(text)
|
||||
if (!key || seen.has(key)) continue
|
||||
seen.add(key)
|
||||
dynamicOut.push(item)
|
||||
}
|
||||
|
||||
for (const item of searchItems) {
|
||||
const text = memoryText(item).trim()
|
||||
if (!text) continue
|
||||
const key = normalizeFactKey(text)
|
||||
if (!key || seen.has(key)) continue
|
||||
seen.add(key)
|
||||
searchOut.push(item)
|
||||
}
|
||||
|
||||
return {
|
||||
static: staticOut,
|
||||
dynamic: dynamicOut,
|
||||
searchResults: mode === "profile" ? [] : searchOut,
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue