fingerprint.ts
1 import { createHash } from 'crypto' 2 import type { AssistantMessage, UserMessage } from '../types/message.js' 3 4 /** 5 * Hardcoded salt from backend validation. 6 * Must match exactly for fingerprint validation to pass. 7 */ 8 export const FINGERPRINT_SALT = '59cf53e54c78' 9 10 /** 11 * Extracts text content from the first user message. 12 * 13 * @param messages - Array of internal message types 14 * @returns First text content, or empty string if not found 15 */ 16 export function extractFirstMessageText( 17 messages: (UserMessage | AssistantMessage)[], 18 ): string { 19 const firstUserMessage = messages.find(msg => msg.type === 'user') 20 if (!firstUserMessage) { 21 return '' 22 } 23 24 const content = firstUserMessage.message.content 25 26 if (typeof content === 'string') { 27 return content 28 } 29 30 if (Array.isArray(content)) { 31 const textBlock = content.find(block => block.type === 'text') 32 if (textBlock && textBlock.type === 'text') { 33 return textBlock.text 34 } 35 } 36 37 return '' 38 } 39 40 /** 41 * Computes 3-character fingerprint for Claude Code attribution. 42 * Algorithm: SHA256(SALT + msg[4] + msg[7] + msg[20] + version)[:3] 43 * IMPORTANT: Do not change this method without careful coordination with 44 * 1P and 3P (Bedrock, Vertex, Azure) APIs. 45 * 46 * @param messageText - First user message text content 47 * @param version - Version string (from MACRO.VERSION) 48 * @returns 3-character hex fingerprint 49 */ 50 export function computeFingerprint( 51 messageText: string, 52 version: string, 53 ): string { 54 // Extract chars at indices [4, 7, 20], use "0" if index not found 55 const indices = [4, 7, 20] 56 const chars = indices.map(i => messageText[i] || '0').join('') 57 58 const fingerprintInput = `${FINGERPRINT_SALT}${chars}${version}` 59 60 // SHA256 hash, return first 3 hex chars 61 const hash = createHash('sha256').update(fingerprintInput).digest('hex') 62 return hash.slice(0, 3) 63 } 64 65 /** 66 * Computes fingerprint from the first user message. 67 * 68 * @param messages - Array of normalized messages 69 * @returns 3-character hex fingerprint 70 */ 71 export function computeFingerprintFromMessages( 72 messages: (UserMessage | AssistantMessage)[], 73 ): string { 74 const firstMessageText = extractFirstMessageText(messages) 75 return computeFingerprint(firstMessageText, MACRO.VERSION) 76 }