fix(integrations): reserve our own slot in the client stack, and drop whole entries

Two review findings on this PR.

All three client-stack implementations appended our entry and then trimmed to
four, so whenever a caller already sent four entries the one dropped was exactly
the one the function exists to add. We vanished from our own stack while every
caller claim survived. The character cap was worse: slicing the joined string
severs an identifier, and the platform parses the fragment as a real client, so a
truncated tail arrives as a client literally named "me". Both caps now drop whole
entries and the reserved slot is ours, in the Python SDK, the TypeScript SDK and
pi-agent. mcp-server has the same fix on the platform branch.

The deepseek comment claimed the backend's allowlist recognizes DEEPSEEK_HARNESS.
This PR introduced that wording, replacing a neutral one. It is not true until
mem0ai/platform#3602 ships, so it now states the dependency.

Two pi-agent tests: our entry survives a full caller stack, and every surviving
entry is whole rather than a severed tail. Python side verified directly, a
4-entry caller stack keeps mem0-python and long entries are dropped whole.

312 passed 8 skipped, pi-agent 96, bundles clean, TS SDK builds.

Claude-Session: https://claude.ai/code/session_01C7tEmH86HAr7GoAAKCEHZb
This commit is contained in:
Saket Aryan
2026-09-17 19:29:13 +05:30
parent a0bbf748c2
commit 3a72dfdc52
5 changed files with 97 additions and 23 deletions
+25 -3
View File
@@ -103,6 +103,30 @@ declare const __MEM0_SDK_VERSION__: string | undefined;
const SDK_VERSION =
typeof __MEM0_SDK_VERSION__ !== "undefined" ? __MEM0_SDK_VERSION__ : "dev";
const MAX_STACK_ENTRIES = 4;
const MAX_STACK_CHARS = 200;
/**
* Append our own entry and bound the result, dropping WHOLE entries.
*
* Neither cap cuts characters: slicing the joined string severs an identifier
* and leaves a fragment the platform parses as a real client name. And the
* reserved slot is ours. Pushing first and then trimming to four dropped exactly
* the entry this exists to add whenever a caller already sent four, so we
* vanished from our own stack while every caller claim survived.
*/
function boundedStack(callerEntries: string[], own: string): string {
const kept: string[] = [];
let budget = MAX_STACK_CHARS - own.length;
for (const entry of callerEntries.slice(0, MAX_STACK_ENTRIES - 1)) {
const cost = entry.length + ", ".length;
if (cost > budget) break;
budget -= cost;
kept.push(entry);
}
return [...kept, own].join(", ");
}
/**
* Surface-identity headers.
*
@@ -121,10 +145,8 @@ function surfaceHeaders(): Record<string, string> {
.map((part) => part.trim())
.filter(Boolean)
: [];
entries.push(`mem0-js/${SDK_VERSION}`);
const headers: Record<string, string> = {
"X-Mem0-Client": entries.slice(0, 4).join(", ").slice(0, 200),
"X-Mem0-Client": boundedStack(entries, `mem0-js/${SDK_VERSION}`),
};
const source = (env.MEM0_SOURCE ?? "").trim();
if (source) headers["X-Mem0-Source"] = source;