{"version":3,"sources":["../../src/telemetry/usage-telemetry.ts"],"names":["path","os","readFileSync","mkdirSync","writeFileSync","isEETelemetryEnabled","hashTelemetryValue","captureTelemetryEvent"],"mappings":"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","file":"index.cjs","sourcesContent":["import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport type { Mastra } from '../mastra';\nimport type { GetMetricBreakdownResponse, ObservabilityStorage } from '../storage/domains';\nimport { captureTelemetryEvent, hashTelemetryValue, isEETelemetryEnabled } from './posthog';\n\nconst INPUT_TOKENS_METRIC = 'mastra_model_total_input_tokens';\nconst OUTPUT_TOKENS_METRIC = 'mastra_model_total_output_tokens';\nconst BREAKDOWN_LIMIT = 200;\n\nexport const USAGE_TELEMETRY_EVENT = 'mastra_model_token_usage';\n\nexport interface SyncUsageTelemetryOptions {\n  /** Override the cursor file location (used by tests). */\n  cursorPath?: string;\n  /** Override the current time (used by tests). */\n  now?: Date;\n}\n\ninterface UsageTelemetryCursors {\n  projects: Record<string, string>;\n}\n\ninterface UsageRow {\n  provider: string | null;\n  model: string | null;\n  inputTokens: number;\n  outputTokens: number;\n  totalInputTokens: number;\n  totalOutputTokens: number;\n}\n\nfunction getDefaultCursorPath(): string {\n  return path.join(os.homedir(), '.mastra', 'usage-telemetry.json');\n}\n\nfunction readCursors(cursorPath: string): UsageTelemetryCursors {\n  try {\n    const parsed = JSON.parse(readFileSync(cursorPath, 'utf-8')) as Partial<UsageTelemetryCursors> | null;\n    if (parsed && typeof parsed === 'object' && parsed.projects && typeof parsed.projects === 'object') {\n      return { projects: parsed.projects };\n    }\n  } catch {\n    // Missing or corrupt cursor file - treat as first sync.\n  }\n  return { projects: {} };\n}\n\nfunction readCursor(cursorPath: string, projectId: string): Date | undefined {\n  const value = readCursors(cursorPath).projects[projectId];\n  if (!value) {\n    return undefined;\n  }\n  const date = new Date(value);\n  return Number.isNaN(date.getTime()) ? undefined : date;\n}\n\nfunction writeCursor(cursorPath: string, projectId: string, syncedAt: Date): void {\n  const cursors = readCursors(cursorPath);\n  cursors.projects[projectId] = syncedAt.toISOString();\n  mkdirSync(path.dirname(cursorPath), { recursive: true });\n  writeFileSync(cursorPath, JSON.stringify(cursors));\n}\n\nfunction applyBreakdown(\n  rows: Map<string, UsageRow>,\n  response: GetMetricBreakdownResponse,\n  field: keyof Pick<UsageRow, 'inputTokens' | 'outputTokens' | 'totalInputTokens' | 'totalOutputTokens'>,\n): void {\n  for (const group of response.groups) {\n    const provider = group.dimensions.provider ?? null;\n    const model = group.dimensions.model ?? null;\n    const key = `${provider}\\u0000${model}`;\n    let row = rows.get(key);\n    if (!row) {\n      row = { provider, model, inputTokens: 0, outputTokens: 0, totalInputTokens: 0, totalOutputTokens: 0 };\n      rows.set(key, row);\n    }\n    row[field] += group.value ?? 0;\n  }\n}\n\n/**\n * Sends aggregated model token usage (input/output tokens per provider+model) to\n * Mastra's anonymous telemetry when the project has observability metrics enabled.\n *\n * Sync strategy: deltas since the last successful sync, tracked per project via a\n * cursor file in `~/.mastra/usage-telemetry.json`. Each event also carries lifetime\n * totals so consumers can read either incremental or cumulative usage. Runs once at\n * server startup; respects `MASTRA_TELEMETRY_DISABLED`. Never throws.\n */\nexport async function syncUsageTelemetry(mastra: Mastra, options: SyncUsageTelemetryOptions = {}): Promise<void> {\n  try {\n    if (!isEETelemetryEnabled()) {\n      return;\n    }\n\n    const observability = mastra.getStorage()?.stores?.observability as ObservabilityStorage | undefined;\n    if (!observability || typeof observability.getMetricBreakdown !== 'function') {\n      return;\n    }\n\n    const projectRoot = process.env.MASTRA_PROJECT_ROOT || process.cwd();\n    const projectId = hashTelemetryValue(projectRoot).slice(0, 16);\n    const cursorPath = options.cursorPath ?? getDefaultCursorPath();\n    const lastSyncedAt = readCursor(cursorPath, projectId);\n    const now = options.now ?? new Date();\n    if (lastSyncedAt && lastSyncedAt.getTime() >= now.getTime()) {\n      return;\n    }\n\n    const baseArgs = {\n      groupBy: ['provider', 'model'],\n      aggregation: 'sum' as const,\n      limit: BREAKDOWN_LIMIT,\n    };\n    const deltaFilters = {\n      timestamp: { ...(lastSyncedAt ? { start: lastSyncedAt, startExclusive: true } : {}), end: now },\n    };\n    const totalFilters = { timestamp: { end: now } };\n\n    const [inputDelta, outputDelta, inputTotal, outputTotal] = await Promise.all([\n      observability.getMetricBreakdown({ ...baseArgs, name: [INPUT_TOKENS_METRIC], filters: deltaFilters }),\n      observability.getMetricBreakdown({ ...baseArgs, name: [OUTPUT_TOKENS_METRIC], filters: deltaFilters }),\n      observability.getMetricBreakdown({ ...baseArgs, name: [INPUT_TOKENS_METRIC], filters: totalFilters }),\n      observability.getMetricBreakdown({ ...baseArgs, name: [OUTPUT_TOKENS_METRIC], filters: totalFilters }),\n    ]);\n\n    const rows = new Map<string, UsageRow>();\n    applyBreakdown(rows, inputDelta, 'inputTokens');\n    applyBreakdown(rows, outputDelta, 'outputTokens');\n    applyBreakdown(rows, inputTotal, 'totalInputTokens');\n    applyBreakdown(rows, outputTotal, 'totalOutputTokens');\n\n    const distinctId = process.env.MASTRA_CLI_DISTINCT_ID || undefined;\n    const command = process.env.MASTRA_TELEMETRY_COMMAND || 'server';\n    const nodeEnv = process.env.NODE_ENV || 'development';\n    const isFirstSync = !lastSyncedAt;\n\n    for (const row of rows.values()) {\n      if (row.inputTokens <= 0 && row.outputTokens <= 0) {\n        continue;\n      }\n      captureTelemetryEvent(USAGE_TELEMETRY_EVENT, distinctId, {\n        provider: row.provider,\n        model: row.model,\n        input_tokens: row.inputTokens,\n        output_tokens: row.outputTokens,\n        total_input_tokens: row.totalInputTokens,\n        total_output_tokens: row.totalOutputTokens,\n        command,\n        node_env: nodeEnv,\n        project_id: projectId,\n        is_first_sync: isFirstSync,\n        window_start: lastSyncedAt?.toISOString() ?? null,\n        window_end: now.toISOString(),\n      });\n    }\n\n    writeCursor(cursorPath, projectId, now);\n  } catch {\n    // Usage telemetry must never affect server startup or runtime behavior.\n  }\n}\n"]}