Files
mem0/integrations/mem0-plugin/.opencode-plugin/telemetry.ts
kartik-mem0 013718c817 refactor(opencode): native SDK tools, drop MCP, trim skills, expand telemetry
Reworks @mem0/opencode-plugin to expose memory operations as native OpenCode
tools (via the @opencode-ai/plugin `tool()` helper, backed by the mem0ai SDK)
instead of delegating to the remote MCP server. Skills load via the `config`
hook (`skills.paths`) instead of being copied into the project `.opencode/`.

- Drop MCP: remove the regex-based MCP tool interception, the bundled
  opencode.json MCP registration, and the cli.ts installer (mem0-opencode bin).
- Native tools: add_memory, search_memories, get_memories, get_memory,
  update_memory, delete_memory, delete_all_memories, delete_entities,
  list_entities, get_event_status.
- Trim skills 16 -> 8 (context-loader, dream, forget, health, peek, pin,
  remember, tour); remove import, export, memory-reviewer, mem0, list-projects,
  switch-project, stats, onboard. De-MCP/onboard wording in kept skills.
- Telemetry: emit the full shared plugin.* schema (adds user_prompt, bash_error,
  pre_compact, session_stop alongside session_start, tool_use); tool_use now
  fires from inside each native tool; add project_hash + os_version props.
- Fix duplicate error-search (single topK:6); correct the documented
  experimental.chat.messages.transform hook name.
- Bump to 0.2.0.
2026-06-16 12:35:58 +05:30

114 lines
3.8 KiB
TypeScript

/**
* Plugin telemetry for the Mem0 OpenCode plugin — anonymous usage tracking
* via PostHog.
*
* Emits the SAME event schema as the Mem0 editor plugin's telemetry.py
* (event names prefixed `plugin.`, `source: "plugin"`, `platform: "opencode"`,
* `distinct_id = sha256(apiKey)[:32]`) so OpenCode shows up as just another
* `platform` value in the shared plugin dashboard instead of a separate
* event namespace.
*
* Fire-and-forget: never throws, never blocks, failures are swallowed. Only
* fires when an API key is present (same as the editor plugin — anonymous
* installs without a key emit nothing). Disable with MEM0_TELEMETRY=false.
*
* Never sends: memory content, API keys, raw user/project IDs. Only sends:
* event type, platform, plugin version, and anonymized hashes of the API key
* and project ID.
*/
import { createHash } from "node:crypto";
import { readFileSync } from "node:fs";
import { release } from "node:os";
const POSTHOG_API_KEY = "phc_hgJkUVJFYtmaJqrvf6CYN67TIQ8yhXAkWzUn9AMU4yX";
const POSTHOG_HOST = "https://us.i.posthog.com/i/v0/e/";
const REQUEST_TIMEOUT_MS = 2_000;
function _loadPluginVersion(): string {
// Source context: telemetry.ts sits next to package.json (./).
// Bundled context: dist/index.js sits one level below it (../).
for (const rel of ["./package.json", "../package.json"]) {
try {
const pkg = JSON.parse(readFileSync(new URL(rel, import.meta.url), "utf-8"));
if (pkg?.name === "@mem0/opencode-plugin" && pkg.version) return pkg.version;
} catch {
/* try next candidate */
}
}
return "unknown";
}
const PLUGIN_VERSION = _loadPluginVersion();
export function isTelemetryEnabled(): boolean {
const val = process.env.MEM0_TELEMETRY;
if (val === undefined) return true;
const s = val.toLowerCase();
return s !== "false" && s !== "0" && s !== "no" && s !== "off";
}
function distinctId(apiKey: string): string {
// Matches telemetry.py `_distinct_id()` so the same user is one person in
// PostHog whether they use OpenCode or any other Mem0 editor plugin.
return createHash("sha256").update(apiKey).digest("hex").slice(0, 32);
}
/**
* Build the PostHog event payload, or null when telemetry is disabled or no
* API key is available. Pure (aside from env/version reads) and exported for
* testing. System-controlled properties are applied last so a caller cannot
* override `source`/`platform`/etc.
*/
export function buildEvent(
eventType: string,
properties: Record<string, unknown>,
apiKey: string | undefined,
projectId?: string,
): Record<string, unknown> | null {
if (!isTelemetryEnabled() || !apiKey) return null;
return {
api_key: POSTHOG_API_KEY,
distinct_id: distinctId(apiKey),
event: `plugin.${eventType}`,
properties: {
...properties,
source: "plugin",
platform: "opencode",
plugin_version: PLUGIN_VERSION,
os: process.platform,
os_version: release(),
sample_rate: 1.0,
$process_person_profile: false,
$lib: "posthog-node",
// Anonymized project segmentation, matching telemetry.py's project_hash.
...(projectId
? { project_hash: createHash("sha256").update(projectId).digest("hex") }
: {}),
},
};
}
/** Send a usage event, fire-and-forget. Never throws, never blocks. */
export function captureEvent(
eventType: string,
properties: Record<string, unknown>,
apiKey: string | undefined,
projectId?: string,
): void {
const payload = buildEvent(eventType, properties, apiKey, projectId);
if (!payload) return;
try {
void fetch(POSTHOG_HOST, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
}).catch(() => {
/* fire-and-forget */
});
} catch {
/* never throw */
}
}