diff --git a/src/agent/client.ts b/src/agent/client.ts index 0b34ddd..9ac8bd7 100644 --- a/src/agent/client.ts +++ b/src/agent/client.ts @@ -23,11 +23,7 @@ export async function getClient() { if (expiresAt - now < 5 * 60 * 1000) { try { // Get or generate device ID (should always exist, but fallback just in case) - let deviceId = settings.deviceId; - if (!deviceId) { - deviceId = crypto.randomUUID(); - settingsManager.updateSettings({ deviceId }); - } + const deviceId = settingsManager.getOrCreateDeviceId(); const deviceName = hostname(); const tokens = await refreshAccessToken( diff --git a/src/auth/setup-ui.tsx b/src/auth/setup-ui.tsx index 1f660c9..e3498ae 100644 --- a/src/auth/setup-ui.tsx +++ b/src/auth/setup-ui.tsx @@ -72,11 +72,7 @@ export function SetupUI({ onComplete }: SetupUIProps) { } // Get or generate device ID - let deviceId = settingsManager.getSetting("deviceId"); - if (!deviceId) { - deviceId = crypto.randomUUID(); - settingsManager.updateSettings({ deviceId }); - } + const deviceId = settingsManager.getOrCreateDeviceId(); const deviceName = hostname(); // Start polling in background diff --git a/src/settings-manager.ts b/src/settings-manager.ts index ccc614c..ccbc568 100644 --- a/src/settings-manager.ts +++ b/src/settings-manager.ts @@ -122,6 +122,19 @@ class SettingsManager { return this.getSettings()[key]; } + /** + * Get or create device ID (generates UUID if not exists) + */ + getOrCreateDeviceId(): string { + const settings = this.getSettings(); + let deviceId = settings.deviceId; + if (!deviceId) { + deviceId = crypto.randomUUID(); + this.updateSettings({ deviceId }); + } + return deviceId; + } + /** * Update settings (synchronous in-memory, async persist) */ diff --git a/src/telemetry/index.ts b/src/telemetry/index.ts index f449307..7f7fd66 100644 --- a/src/telemetry/index.ts +++ b/src/telemetry/index.ts @@ -1,3 +1,5 @@ +import { settingsManager } from "../settings-manager"; + export interface TelemetryEvent { type: "session_start" | "session_end" | "tool_usage" | "error" | "user_input"; timestamp: string; @@ -54,6 +56,7 @@ export interface UserInputData { class TelemetryManager { private events: TelemetryEvent[] = []; private sessionId: string; + private deviceId: string | null = null; private currentAgentId: string | null = null; private sessionStartTime: number; private messageCount = 0; @@ -95,18 +98,6 @@ class TelemetryManager { return false; } - // Disable telemetry if using self-hosted server (not api.letta.com) - const baseURL = process.env.LETTA_BASE_URL; - if (baseURL && !baseURL.includes("api.letta.com")) { - return false; - } - - // Disable telemetry if no API key is set - const apiKey = process.env.LETTA_API_KEY; - if (!apiKey) { - return false; - } - return true; } @@ -118,6 +109,9 @@ class TelemetryManager { return; } + // Initialize device ID (persistent across sessions) + this.deviceId = settingsManager.getOrCreateDeviceId(); + this.trackSessionStart(); // Set up periodic flushing @@ -180,6 +174,7 @@ class TelemetryManager { data: { ...data, session_id: this.sessionId, + device_id: this.deviceId || undefined, agent_id: this.currentAgentId || undefined, }, }; @@ -382,7 +377,6 @@ class TelemetryManager { const eventsToSend = [...this.events]; this.events = []; - const baseURL = process.env.LETTA_BASE_URL || "https://api.letta.com"; const apiKey = process.env.LETTA_API_KEY; try { @@ -391,18 +385,22 @@ class TelemetryManager { setTimeout(() => reject(new Error("Telemetry request timeout")), 5000), ); - const fetchPromise = fetch(`${baseURL}/v1/metadata/telemetry`, { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${apiKey}`, - "X-Letta-Source": "letta-code", + const fetchPromise = fetch( + "https://api.letta.com/v1/metadata/telemetry", + { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${apiKey}`, + "X-Letta-Source": "letta-code", + "X-Letta-Code-Device-ID": this.deviceId || "", + }, + body: JSON.stringify({ + service: "letta-code", + events: eventsToSend, + }), }, - body: JSON.stringify({ - service: "letta-code", - events: eventsToSend, - }), - }); + ); const response = (await Promise.race([ fetchPromise,