feat(deepseek-plugin,mem0-strands): add usage telemetry (#7110)

This commit is contained in:
Kartik
2026-08-27 13:48:30 +05:30
committed by GitHub
parent 39bc023305
commit 0070e08e01
13 changed files with 785 additions and 6 deletions
+53 -1
View File
@@ -16,6 +16,7 @@ import { MemoryClient } from "mem0ai";
import { formatMemoryList, formatAddResult } from "./formatting.ts";
import { truncateOutput } from "./output.ts";
import { resolveSearchFilters, resolveAddParams } from "./scoping.ts";
import { captureEvent, errorKind } from "./telemetry.ts";
export const name = "mem0";
export const inject = ["tools"];
@@ -81,6 +82,8 @@ export function apply(ctx: Context, config: Config): void {
...(config.host ? { host: config.host } : {}),
});
captureEvent("deepseek.plugin.mounted", { has_host: Boolean(config.host) }, client);
// Recall. The platform rejects top-level entity params on search, so scope
// goes inside `filters` (unlike add below, which takes them top-level).
ctx.tools.register(
@@ -99,11 +102,36 @@ export function apply(ctx: Context, config: Config): void {
output: textOutput,
async execute({ query, limit, userId: u, agentId, runId }) {
const filters = resolveSearchFilters({ userId: u, agentId, runId }, userId);
const topK = limit && limit > 0 ? limit : DEFAULT_SEARCH_LIMIT;
const started = Date.now();
try {
const topK = limit && limit > 0 ? limit : DEFAULT_SEARCH_LIMIT;
const { results } = await client.search(query, { filters, topK });
captureEvent(
"deepseek.tool.search_memory",
{
success: true,
duration_ms: Date.now() - started,
top_k: topK,
result_count: results?.length ?? 0,
query_chars: query.length,
scope_overridden: Boolean(u && u !== userId),
has_agent_id: Boolean(agentId),
has_run_id: Boolean(runId),
},
client,
);
return truncateOutput(formatMemoryList(results ?? []));
} catch (err) {
captureEvent(
"deepseek.tool.search_memory",
{
success: false,
duration_ms: Date.now() - started,
top_k: topK,
error_kind: errorKind(err),
},
client,
);
return `search_memory failed: ${err instanceof Error ? err.message : String(err)}`;
}
},
@@ -123,13 +151,37 @@ export function apply(ctx: Context, config: Config): void {
output: textOutput,
async execute({ text, userId: u, agentId, runId }) {
const addParams = resolveAddParams({ userId: u, agentId, runId }, userId);
const started = Date.now();
try {
const result = await client.add([{ role: "user", content: text }], {
...addParams,
source: SOURCE,
});
captureEvent(
"deepseek.tool.add_memory",
{
success: true,
duration_ms: Date.now() - started,
text_chars: text.length,
memory_count: Array.isArray(result) ? result.length : 0,
scope_overridden: Boolean(u && u !== userId),
has_agent_id: Boolean(agentId),
has_run_id: Boolean(runId),
},
client,
);
return truncateOutput(formatAddResult(result));
} catch (err) {
captureEvent(
"deepseek.tool.add_memory",
{
success: false,
duration_ms: Date.now() - started,
text_chars: text.length,
error_kind: errorKind(err),
},
client,
);
return `add_memory failed: ${err instanceof Error ? err.message : String(err)}`;
}
},
@@ -0,0 +1,173 @@
/**
* Anonymous usage telemetry for the DeepSeek Harness plugin.
*
* Fire-and-forget PostHog events over native fetch, batched and flushed every
* 5 seconds, at 10 queued events, or on process exit. Never throws, never logs.
*
* Events carry only tool names, durations, counts, and coarse failure kinds:
* never queries, memory text, filters, or API keys.
*
* Disable with MEM0_TELEMETRY=false.
*/
import { randomUUID } from "node:crypto";
import * as fs from "node:fs";
import * as os from "node:os";
import * as path from "node:path";
const POSTHOG_API_KEY = "phc_hgJkUVJFYtmaJqrvf6CYN67TIQ8yhXAkWzUn9AMU4yX";
const POSTHOG_BATCH_URL = "https://us.i.posthog.com/batch/";
const FLUSH_INTERVAL_MS = 5_000;
const FLUSH_THRESHOLD = 10;
const SEND_TIMEOUT_MS = 3_000;
const PLUGIN_VERSION = ((): string => {
try {
return JSON.parse(
fs.readFileSync(new URL("../package.json", import.meta.url), "utf-8"),
).version;
} catch {
return "unknown";
}
})();
/** The Mem0 SDK resolves this to the account email once ping() lands, so events join other Mem0 surfaces. */
export interface TelemetryIdentity {
telemetryId?: string;
}
let queue: Record<string, unknown>[] = [];
let flushTimer: ReturnType<typeof setInterval> | undefined;
let exitHandlerInstalled = false;
let cachedAnonymousId: string | undefined;
let identified = false;
function identityPath(): string {
return path.join(os.homedir(), ".mem0", "deepseek-plugin-telemetry.json");
}
export function isTelemetryEnabled(): boolean {
const value = process.env.MEM0_TELEMETRY?.toLowerCase();
return value !== "false" && value !== "0" && value !== "no" && value !== "off";
}
function anonymousId(): string {
if (cachedAnonymousId) return cachedAnonymousId;
try {
const stored = JSON.parse(fs.readFileSync(identityPath(), "utf-8"));
if (typeof stored.anonymousId === "string" && stored.anonymousId) {
cachedAnonymousId = stored.anonymousId;
return cachedAnonymousId!;
}
} catch {
/* first run, or an unreadable identity file */
}
const created = `deepseek-anon-${randomUUID().replace(/-/g, "")}`;
try {
const target = identityPath();
fs.mkdirSync(path.dirname(target), { recursive: true });
fs.writeFileSync(target, JSON.stringify({ anonymousId: created }), "utf-8");
} catch {
/* an unwritable home directory must not break the plugin */
}
cachedAnonymousId = created;
return created;
}
/** Merge the pre-ping anonymous history into the account once the email resolves. */
function identifyEvent(distinctId: string): Record<string, unknown> | undefined {
if (identified || distinctId.startsWith("deepseek-anon-")) return undefined;
identified = true;
let storedAnonymousId: string | undefined;
try {
storedAnonymousId = JSON.parse(fs.readFileSync(identityPath(), "utf-8")).anonymousId;
fs.unlinkSync(identityPath());
} catch {
return undefined;
}
cachedAnonymousId = undefined;
if (!storedAnonymousId) return undefined;
return {
event: "$identify",
distinct_id: distinctId,
properties: { $anon_distinct_id: storedAnonymousId, $lib: "posthog-node" },
};
}
export function flushEvents(): void {
if (queue.length === 0) return;
const batch = queue;
queue = [];
fetch(POSTHOG_BATCH_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ api_key: POSTHOG_API_KEY, batch }),
signal: AbortSignal.timeout(SEND_TIMEOUT_MS),
}).catch(() => {
/* telemetry never surfaces its own failures */
});
}
function scheduleFlush(): void {
if (!flushTimer) {
flushTimer = setInterval(flushEvents, FLUSH_INTERVAL_MS);
flushTimer.unref?.();
}
if (!exitHandlerInstalled) {
exitHandlerInstalled = true;
process.on("beforeExit", flushEvents);
}
}
export function errorKind(error: unknown): string {
const text = (error instanceof Error ? error.message : String(error)).toLowerCase();
if (text.includes("timeout") || text.includes("aborted")) return "timeout";
if (text.includes("401") || text.includes("403") || text.includes("unauthor")) return "auth";
if (text.includes("429") || text.includes("rate limit")) return "rate-limited";
if (/50[0234]/.test(text)) return "server-error";
if (text.includes("400") || text.includes("422")) return "bad-request";
if (text.includes("fetch failed") || text.includes("enotfound")) return "network";
return error instanceof Error ? error.constructor.name : "other";
}
export function captureEvent(
event: string,
properties: Record<string, unknown>,
client: TelemetryIdentity,
): void {
if (!isTelemetryEnabled()) return;
try {
const distinctId = client.telemetryId || anonymousId();
const identify = identifyEvent(distinctId);
if (identify) queue.push(identify);
queue.push({
event,
distinct_id: distinctId,
properties: {
source: "DEEPSEEK_HARNESS",
language: "node",
plugin_version: PLUGIN_VERSION,
node_version: process.version,
os: process.platform,
$process_person_profile: false,
$lib: "posthog-node",
...properties,
},
});
scheduleFlush();
if (queue.length >= FLUSH_THRESHOLD) flushEvents();
} catch {
/* telemetry never breaks a tool call */
}
}
export function _queueForTesting(): Record<string, unknown>[] {
return queue;
}
export function _resetForTesting(): void {
queue = [];
clearInterval(flushTimer);
flushTimer = undefined;
cachedAnonymousId = undefined;
identified = false;
}