Files
mem0/mem0-ts/src/client/telemetry.ts
T
Younes Slaoui f34e42eef3 fix(telemetry): only stitch oss identities with evidence of real oss usage
MemoryClient init fired PostHog $identify for anon ids the SDKs had minted
themselves (Python: setup_config() at import time; TS: getOrCreateMem0UserId()
inside the alias path), so ~90% of stitches merged event-less anon persons
into platform users, worst in ephemeral containers.

- never create the anon id from platform client paths; Python mints lazily in
  Memory/AsyncMemory init, gated on MEM0_TELEMETRY
- record telemetry.oss_used_at when OSS Memory genuinely initializes (both
  SDKs, shared ~/.mem0/config.json key) and only stitch the oss user_id when
  the marker is present; cli anon id stays presence-based
- stamp client_version on $identify so fixed-SDK stitches are separable from
  old-SDK noise in PostHog
2026-06-11 12:55:57 -07:00

155 lines
3.9 KiB
TypeScript

// @ts-nocheck
import type { TelemetryClient, TelemetryOptions } from "./telemetry.types";
// __MEM0_SDK_VERSION__ is inlined by tsup/esbuild's `define` at build time from
// package.json. In unbundled environments (ts-jest, jest globalSetup) the
// identifier is not defined, so guard with typeof to fall back safely.
let version =
typeof __MEM0_SDK_VERSION__ !== "undefined" ? __MEM0_SDK_VERSION__ : "dev";
// Safely check for process.env in different environments
let MEM0_TELEMETRY = true;
try {
MEM0_TELEMETRY = process?.env?.MEM0_TELEMETRY === "false" ? false : true;
} catch (error) {}
const POSTHOG_API_KEY = "phc_hgJkUVJFYtmaJqrvf6CYN67TIQ8yhXAkWzUn9AMU4yX";
const POSTHOG_HOST = "https://us.i.posthog.com/i/v0/e/";
// Simple hash function using random strings
function generateHash(input: string): string {
const randomStr =
Math.random().toString(36).substring(2, 15) +
Math.random().toString(36).substring(2, 15);
return randomStr;
}
class UnifiedTelemetry implements TelemetryClient {
private apiKey: string;
private host: string;
constructor(projectApiKey: string, host: string) {
this.apiKey = projectApiKey;
this.host = host;
}
async captureEvent(
distinctId: string,
eventName: string,
properties = {},
): Promise<boolean> {
if (!MEM0_TELEMETRY) return false;
const eventProperties = {
client_version: version,
timestamp: new Date().toISOString(),
...properties,
$process_person_profile: false,
$lib: "posthog-node",
};
const payload = {
api_key: this.apiKey,
distinct_id: distinctId,
event: eventName,
properties: eventProperties,
};
try {
const response = await fetch(this.host, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
});
if (!response.ok) {
console.error("Telemetry event capture failed:", await response.text());
return false;
}
return true;
} catch (error) {
console.error("Telemetry event capture failed:", error);
return false;
}
}
async captureIdentify(anonId: string, email: string): Promise<boolean> {
if (!MEM0_TELEMETRY) return false;
if (!anonId || !email || anonId === email) return false;
const payload = {
api_key: this.apiKey,
distinct_id: email,
event: "$identify",
properties: {
$anon_distinct_id: anonId,
client_source: "typescript",
client_version: version,
$lib: "posthog-node",
},
};
try {
const response = await fetch(this.host, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
});
if (!response.ok) {
console.error(
"Telemetry identify capture failed:",
await response.text(),
);
return false;
}
return true;
} catch (error) {
console.error("Telemetry identify capture failed:", error);
return false;
}
}
async shutdown() {
// No shutdown needed for direct API calls
}
}
function isTelemetryEnabled(): boolean {
return MEM0_TELEMETRY;
}
const telemetry = new UnifiedTelemetry(POSTHOG_API_KEY, POSTHOG_HOST);
async function captureClientEvent(
eventName: string,
instance: any,
additionalData = {},
) {
if (!instance.telemetryId) {
console.warn("No telemetry ID found for instance");
return;
}
const eventData = {
function: `${instance.constructor.name}`,
method: eventName,
api_host: instance.host,
timestamp: new Date().toISOString(),
client_version: version,
keys: additionalData?.keys || [],
...additionalData,
};
await telemetry.captureEvent(
instance.telemetryId,
`client.${eventName}`,
eventData,
);
}
export { telemetry, captureClientEvent, generateHash, isTelemetryEnabled };