File size: 3,812 Bytes
c7052c4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
/**
 * OpenAI-compatible ID generator utility
 *
 * OpenAI ID patterns:
 * - Chat completions: chatcmpl-{29 alphanumeric chars}
 * - Tool calls: call_{24 alphanumeric chars}
 *
 * Total lengths:
 * - chatcmpl- prefix (8 chars) + 29 = 37 characters
 * - call_ prefix (5 chars) + 24 = 29 characters
 */

// Default sizes matching OpenAI's observed patterns
export const OPENAI_ID_DEFAULTS = {
  CHAT_COMPLETION_LENGTH: 29,
  TOOL_CALL_LENGTH: 24,
  BATCH_ID_LENGTH: 24,
  FILE_ID_LENGTH: 24,
};

// Prefixes used by OpenAI
export const OPENAI_ID_PREFIXES = {
  CHAT_COMPLETION: 'chatcmpl-',
  TOOL_CALL: 'call_',
  BATCH: 'batch_',
  FILE: 'file-',
};

const ALPHANUMERIC_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';

/**
 * Generates a random alphanumeric string of specified length
 * @param length - The length of the random string to generate
 * @returns Random alphanumeric string
 */
export function generateRandomAlphanumeric(length: number): string {
  const array = new Uint8Array(length);
  crypto.getRandomValues(array);

  let result = '';
  for (let i = 0; i < length; i++) {
    result += ALPHANUMERIC_CHARS[array[i] % ALPHANUMERIC_CHARS.length];
  }
  return result;
}

/**
 * Validates if an ID matches OpenAI's expected format
 * @param id - The ID to validate
 * @param prefix - Expected prefix
 * @param minLength - Minimum length of the random part (optional)
 * @param maxLength - Maximum length of the random part (optional)
 * @returns boolean indicating if the ID is valid
 */
export type IdType = 'chatCompletion' | 'toolCall' | 'batch' | 'file' | 'rerank' | 'custom';

interface GenerateIdOptions {
  type: IdType;
  customPrefix?: string;
  length?: number;
}

/**
 * Generates an OpenAI-compatible ID
 * @param options - Configuration options for ID generation
 * @returns Generated ID string
 */
export function generateOpenAICompatibleId(options: GenerateIdOptions): string {
  const { type, customPrefix, length } = options;

  let prefix: string;
  let randomLength: number;

  switch (type) {
    case 'chatCompletion':
      prefix = OPENAI_ID_PREFIXES.CHAT_COMPLETION;
      randomLength = length ?? OPENAI_ID_DEFAULTS.CHAT_COMPLETION_LENGTH;
      break;
    case 'toolCall':
      prefix = OPENAI_ID_PREFIXES.TOOL_CALL;
      randomLength = length ?? OPENAI_ID_DEFAULTS.TOOL_CALL_LENGTH;
      break;
    case 'batch':
      prefix = OPENAI_ID_PREFIXES.BATCH;
      randomLength = length ?? OPENAI_ID_DEFAULTS.BATCH_ID_LENGTH;
      break;
    case 'file':
      prefix = OPENAI_ID_PREFIXES.FILE;
      randomLength = length ?? OPENAI_ID_DEFAULTS.FILE_ID_LENGTH;
      break;
    case 'rerank':
      prefix = 'rerank_';
      randomLength = length ?? OPENAI_ID_DEFAULTS.CHAT_COMPLETION_LENGTH;
      break;
    case 'custom':
      prefix = customPrefix ?? '';
      randomLength = length ?? OPENAI_ID_DEFAULTS.CHAT_COMPLETION_LENGTH;
      break;
    default:
      prefix = OPENAI_ID_PREFIXES.CHAT_COMPLETION;
      randomLength = OPENAI_ID_DEFAULTS.CHAT_COMPLETION_LENGTH;
  }

  return prefix + generateRandomAlphanumeric(randomLength);
}

/**
 * Gets an ID, honoring user-provided or upstream ID if valid, otherwise generates a new one
 * @param providedId - User-provided or upstream ID (optional)
 * @param type - Type of ID for generation/validation
 * @param options - Additional options
 * @returns Valid ID (provided or generated)
 */
export function getOrGenerateId(
  providedId: string | undefined | null,
  type: IdType,
  options?: {
    customPrefix?: string;
    length?: number;
  },
): string {
  const { customPrefix, length } = options ?? {};

  if (providedId && typeof providedId === 'string' && providedId.trim() !== '') {
    return providedId;
  }

  return generateOpenAICompatibleId({ type, customPrefix, length });
}