/ utils / fingerprint.ts
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  }