/ src / utils / fileOperationAnalytics.ts
fileOperationAnalytics.ts
 1  import { createHash } from 'crypto'
 2  import type { AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS } from 'src/services/analytics/index.js'
 3  import { logEvent } from 'src/services/analytics/index.js'
 4  
 5  /**
 6   * Creates a truncated SHA256 hash (16 chars) for file paths
 7   * Used for privacy-preserving analytics on file operations
 8   */
 9  function hashFilePath(
10    filePath: string,
11  ): AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS {
12    return createHash('sha256')
13      .update(filePath)
14      .digest('hex')
15      .slice(0, 16) as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS
16  }
17  
18  /**
19   * Creates a full SHA256 hash (64 chars) for file contents
20   * Used for deduplication and change detection analytics
21   */
22  function hashFileContent(
23    content: string,
24  ): AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS {
25    return createHash('sha256')
26      .update(content)
27      .digest('hex') as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS
28  }
29  
30  // Maximum content size to hash (100KB)
31  // Prevents memory exhaustion when hashing large files (e.g., base64-encoded images)
32  const MAX_CONTENT_HASH_SIZE = 100 * 1024
33  
34  /**
35   * Logs file operation analytics to Statsig
36   */
37  export function logFileOperation(params: {
38    operation: 'read' | 'write' | 'edit'
39    tool: 'FileReadTool' | 'FileWriteTool' | 'FileEditTool'
40    filePath: string
41    content?: string
42    type?: 'create' | 'update'
43  }): void {
44    const metadata: Record<
45      string,
46      | AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS
47      | number
48      | boolean
49    > = {
50      operation:
51        params.operation as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
52      tool: params.tool as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
53      filePathHash: hashFilePath(params.filePath),
54    }
55  
56    // Only hash content if it's provided and below size limit
57    // This prevents memory exhaustion from hashing large files (e.g., base64-encoded images)
58    if (
59      params.content !== undefined &&
60      params.content.length <= MAX_CONTENT_HASH_SIZE
61    ) {
62      metadata.contentHash = hashFileContent(params.content)
63    }
64  
65    if (params.type !== undefined) {
66      metadata.type =
67        params.type as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS
68    }
69  
70    logEvent('tengu_file_operation', metadata)
71  }