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 }