@revenium/openai 1.0.13 → 1.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (228) hide show
  1. package/.env.example +10 -15
  2. package/CHANGELOG.md +44 -11
  3. package/CODE_OF_CONDUCT.md +57 -0
  4. package/CONTRIBUTING.md +38 -0
  5. package/README.md +104 -216
  6. package/SECURITY.md +34 -0
  7. package/dist/cjs/core/client/index.js +14 -0
  8. package/dist/cjs/core/client/index.js.map +1 -0
  9. package/dist/cjs/core/client/manager.js +109 -0
  10. package/dist/cjs/core/client/manager.js.map +1 -0
  11. package/dist/cjs/core/config/azure-config.js +5 -17
  12. package/dist/cjs/core/config/azure-config.js.map +1 -1
  13. package/dist/cjs/core/config/index.js +2 -2
  14. package/dist/cjs/core/config/index.js.map +1 -1
  15. package/dist/cjs/core/config/loader.js +34 -14
  16. package/dist/cjs/core/config/loader.js.map +1 -1
  17. package/dist/cjs/core/config/manager.js +11 -5
  18. package/dist/cjs/core/config/manager.js.map +1 -1
  19. package/dist/cjs/core/config/validator.js +3 -45
  20. package/dist/cjs/core/config/validator.js.map +1 -1
  21. package/dist/cjs/core/middleware/index.js +17 -0
  22. package/dist/cjs/core/middleware/index.js.map +1 -0
  23. package/dist/cjs/core/middleware/interfaces.js +361 -0
  24. package/dist/cjs/core/middleware/interfaces.js.map +1 -0
  25. package/dist/cjs/core/middleware/revenium-client.js +142 -0
  26. package/dist/cjs/core/middleware/revenium-client.js.map +1 -0
  27. package/dist/cjs/core/providers/detector.js +45 -23
  28. package/dist/cjs/core/providers/detector.js.map +1 -1
  29. package/dist/cjs/core/providers/index.js +2 -1
  30. package/dist/cjs/core/providers/index.js.map +1 -1
  31. package/dist/cjs/core/tracking/api-client.js +14 -13
  32. package/dist/cjs/core/tracking/api-client.js.map +1 -1
  33. package/dist/cjs/core/tracking/payload-builder.js +15 -25
  34. package/dist/cjs/core/tracking/payload-builder.js.map +1 -1
  35. package/dist/cjs/core/tracking/usage-tracker.js +22 -18
  36. package/dist/cjs/core/tracking/usage-tracker.js.map +1 -1
  37. package/dist/cjs/index.js +26 -195
  38. package/dist/cjs/index.js.map +1 -1
  39. package/dist/cjs/types/index.js +0 -8
  40. package/dist/cjs/types/index.js.map +1 -1
  41. package/dist/cjs/types/openai-augmentation.js +0 -49
  42. package/dist/cjs/types/openai-augmentation.js.map +1 -1
  43. package/dist/cjs/utils/constants.js +17 -20
  44. package/dist/cjs/utils/constants.js.map +1 -1
  45. package/dist/cjs/utils/error-handler.js +18 -14
  46. package/dist/cjs/utils/error-handler.js.map +1 -1
  47. package/dist/cjs/utils/metadata-builder.js +17 -16
  48. package/dist/cjs/utils/metadata-builder.js.map +1 -1
  49. package/dist/cjs/utils/provider-detection.js +25 -28
  50. package/dist/cjs/utils/provider-detection.js.map +1 -1
  51. package/dist/esm/core/client/index.js +6 -0
  52. package/dist/esm/core/client/index.js.map +1 -0
  53. package/dist/esm/core/client/manager.js +102 -0
  54. package/dist/esm/core/client/manager.js.map +1 -0
  55. package/dist/esm/core/config/azure-config.js +6 -18
  56. package/dist/esm/core/config/azure-config.js.map +1 -1
  57. package/dist/esm/core/config/index.js +5 -4
  58. package/dist/esm/core/config/index.js.map +1 -1
  59. package/dist/esm/core/config/loader.js +33 -13
  60. package/dist/esm/core/config/loader.js.map +1 -1
  61. package/dist/esm/core/config/manager.js +13 -7
  62. package/dist/esm/core/config/manager.js.map +1 -1
  63. package/dist/esm/core/config/validator.js +3 -44
  64. package/dist/esm/core/config/validator.js.map +1 -1
  65. package/dist/esm/core/middleware/index.js +8 -0
  66. package/dist/esm/core/middleware/index.js.map +1 -0
  67. package/dist/esm/core/middleware/interfaces.js +353 -0
  68. package/dist/esm/core/middleware/interfaces.js.map +1 -0
  69. package/dist/esm/core/middleware/revenium-client.js +105 -0
  70. package/dist/esm/core/middleware/revenium-client.js.map +1 -0
  71. package/dist/esm/core/providers/detector.js +43 -22
  72. package/dist/esm/core/providers/detector.js.map +1 -1
  73. package/dist/esm/core/providers/index.js +2 -2
  74. package/dist/esm/core/providers/index.js.map +1 -1
  75. package/dist/esm/core/tracking/api-client.js +13 -12
  76. package/dist/esm/core/tracking/api-client.js.map +1 -1
  77. package/dist/esm/core/tracking/payload-builder.js +16 -26
  78. package/dist/esm/core/tracking/payload-builder.js.map +1 -1
  79. package/dist/esm/core/tracking/usage-tracker.js +24 -20
  80. package/dist/esm/core/tracking/usage-tracker.js.map +1 -1
  81. package/dist/esm/index.js +9 -177
  82. package/dist/esm/index.js.map +1 -1
  83. package/dist/esm/types/index.js +2 -10
  84. package/dist/esm/types/index.js.map +1 -1
  85. package/dist/esm/types/openai-augmentation.js +0 -49
  86. package/dist/esm/types/openai-augmentation.js.map +1 -1
  87. package/dist/esm/utils/constants.js +16 -19
  88. package/dist/esm/utils/constants.js.map +1 -1
  89. package/dist/esm/utils/error-handler.js +19 -15
  90. package/dist/esm/utils/error-handler.js.map +1 -1
  91. package/dist/esm/utils/metadata-builder.js +17 -16
  92. package/dist/esm/utils/metadata-builder.js.map +1 -1
  93. package/dist/esm/utils/provider-detection.js +26 -29
  94. package/dist/esm/utils/provider-detection.js.map +1 -1
  95. package/dist/types/core/client/index.d.ts +6 -0
  96. package/dist/types/core/client/index.d.ts.map +1 -0
  97. package/dist/types/core/client/manager.d.ts +32 -0
  98. package/dist/types/core/client/manager.d.ts.map +1 -0
  99. package/dist/types/core/config/azure-config.d.ts +2 -2
  100. package/dist/types/core/config/azure-config.d.ts.map +1 -1
  101. package/dist/types/core/config/index.d.ts +4 -4
  102. package/dist/types/core/config/index.d.ts.map +1 -1
  103. package/dist/types/core/config/loader.d.ts +3 -1
  104. package/dist/types/core/config/loader.d.ts.map +1 -1
  105. package/dist/types/core/config/manager.d.ts +1 -1
  106. package/dist/types/core/config/manager.d.ts.map +1 -1
  107. package/dist/types/core/config/validator.d.ts +1 -12
  108. package/dist/types/core/config/validator.d.ts.map +1 -1
  109. package/dist/types/core/middleware/index.d.ts +8 -0
  110. package/dist/types/core/middleware/index.d.ts.map +1 -0
  111. package/dist/types/core/middleware/interfaces.d.ts +74 -0
  112. package/dist/types/core/middleware/interfaces.d.ts.map +1 -0
  113. package/dist/types/core/middleware/revenium-client.d.ts +58 -0
  114. package/dist/types/core/middleware/revenium-client.d.ts.map +1 -0
  115. package/dist/types/core/providers/detector.d.ts +9 -2
  116. package/dist/types/core/providers/detector.d.ts.map +1 -1
  117. package/dist/types/core/providers/index.d.ts +2 -2
  118. package/dist/types/core/providers/index.d.ts.map +1 -1
  119. package/dist/types/core/tracking/api-client.d.ts +1 -1
  120. package/dist/types/core/tracking/api-client.d.ts.map +1 -1
  121. package/dist/types/core/tracking/payload-builder.d.ts +3 -3
  122. package/dist/types/core/tracking/payload-builder.d.ts.map +1 -1
  123. package/dist/types/core/tracking/usage-tracker.d.ts +2 -2
  124. package/dist/types/core/tracking/usage-tracker.d.ts.map +1 -1
  125. package/dist/types/index.d.ts +11 -135
  126. package/dist/types/index.d.ts.map +1 -1
  127. package/dist/types/types/function-parameters.d.ts +2 -23
  128. package/dist/types/types/function-parameters.d.ts.map +1 -1
  129. package/dist/types/types/index.d.ts +11 -105
  130. package/dist/types/types/index.d.ts.map +1 -1
  131. package/dist/types/types/openai-augmentation.d.ts +4 -138
  132. package/dist/types/types/openai-augmentation.d.ts.map +1 -1
  133. package/dist/types/utils/constants.d.ts +7 -1
  134. package/dist/types/utils/constants.d.ts.map +1 -1
  135. package/dist/types/utils/error-handler.d.ts +2 -2
  136. package/dist/types/utils/error-handler.d.ts.map +1 -1
  137. package/dist/types/utils/metadata-builder.d.ts +2 -2
  138. package/dist/types/utils/metadata-builder.d.ts.map +1 -1
  139. package/dist/types/utils/provider-detection.d.ts +3 -3
  140. package/dist/types/utils/provider-detection.d.ts.map +1 -1
  141. package/examples/README.md +282 -198
  142. package/examples/azure/basic.ts +62 -0
  143. package/examples/azure/responses-basic.ts +45 -0
  144. package/examples/azure/responses-stream.ts +61 -0
  145. package/examples/azure/stream.ts +56 -0
  146. package/examples/getting_started.ts +31 -43
  147. package/examples/openai/basic.ts +45 -0
  148. package/examples/openai/metadata.ts +67 -0
  149. package/examples/openai/responses-basic.ts +44 -0
  150. package/examples/openai/responses-embed.ts +34 -0
  151. package/examples/openai/responses-streaming.ts +63 -0
  152. package/examples/openai/streaming.ts +59 -0
  153. package/package.json +20 -13
  154. package/dist/cjs/core/wrapper/index.js +0 -15
  155. package/dist/cjs/core/wrapper/index.js.map +0 -1
  156. package/dist/cjs/core/wrapper/instance-patcher.js +0 -202
  157. package/dist/cjs/core/wrapper/instance-patcher.js.map +0 -1
  158. package/dist/cjs/core/wrapper/request-handler.js +0 -317
  159. package/dist/cjs/core/wrapper/request-handler.js.map +0 -1
  160. package/dist/cjs/core/wrapper/stream-wrapper.js +0 -82
  161. package/dist/cjs/core/wrapper/stream-wrapper.js.map +0 -1
  162. package/dist/cjs/utils/azure-model-resolver.js +0 -211
  163. package/dist/cjs/utils/azure-model-resolver.js.map +0 -1
  164. package/dist/cjs/utils/request-handler-factory.js +0 -185
  165. package/dist/cjs/utils/request-handler-factory.js.map +0 -1
  166. package/dist/esm/core/wrapper/index.js +0 -9
  167. package/dist/esm/core/wrapper/index.js.map +0 -1
  168. package/dist/esm/core/wrapper/instance-patcher.js +0 -199
  169. package/dist/esm/core/wrapper/instance-patcher.js.map +0 -1
  170. package/dist/esm/core/wrapper/request-handler.js +0 -310
  171. package/dist/esm/core/wrapper/request-handler.js.map +0 -1
  172. package/dist/esm/core/wrapper/stream-wrapper.js +0 -79
  173. package/dist/esm/core/wrapper/stream-wrapper.js.map +0 -1
  174. package/dist/esm/utils/azure-model-resolver.js +0 -204
  175. package/dist/esm/utils/azure-model-resolver.js.map +0 -1
  176. package/dist/esm/utils/request-handler-factory.js +0 -146
  177. package/dist/esm/utils/request-handler-factory.js.map +0 -1
  178. package/dist/types/core/wrapper/index.d.ts +0 -8
  179. package/dist/types/core/wrapper/index.d.ts.map +0 -1
  180. package/dist/types/core/wrapper/instance-patcher.d.ts +0 -33
  181. package/dist/types/core/wrapper/instance-patcher.d.ts.map +0 -1
  182. package/dist/types/core/wrapper/request-handler.d.ts +0 -29
  183. package/dist/types/core/wrapper/request-handler.d.ts.map +0 -1
  184. package/dist/types/core/wrapper/stream-wrapper.d.ts +0 -13
  185. package/dist/types/core/wrapper/stream-wrapper.d.ts.map +0 -1
  186. package/dist/types/utils/azure-model-resolver.d.ts +0 -41
  187. package/dist/types/utils/azure-model-resolver.d.ts.map +0 -1
  188. package/dist/types/utils/request-handler-factory.d.ts +0 -81
  189. package/dist/types/utils/request-handler-factory.d.ts.map +0 -1
  190. package/examples/azure-basic.ts +0 -206
  191. package/examples/azure-responses-basic.ts +0 -233
  192. package/examples/azure-responses-streaming.ts +0 -255
  193. package/examples/azure-streaming.ts +0 -209
  194. package/examples/openai-basic.ts +0 -147
  195. package/examples/openai-function-calling.ts +0 -259
  196. package/examples/openai-responses-basic.ts +0 -212
  197. package/examples/openai-responses-streaming.ts +0 -232
  198. package/examples/openai-streaming.ts +0 -172
  199. package/examples/openai-vision.ts +0 -289
  200. package/src/core/config/azure-config.ts +0 -72
  201. package/src/core/config/index.ts +0 -23
  202. package/src/core/config/loader.ts +0 -66
  203. package/src/core/config/manager.ts +0 -95
  204. package/src/core/config/validator.ts +0 -89
  205. package/src/core/providers/detector.ts +0 -159
  206. package/src/core/providers/index.ts +0 -16
  207. package/src/core/tracking/api-client.ts +0 -78
  208. package/src/core/tracking/index.ts +0 -21
  209. package/src/core/tracking/payload-builder.ts +0 -137
  210. package/src/core/tracking/usage-tracker.ts +0 -189
  211. package/src/core/wrapper/index.ts +0 -9
  212. package/src/core/wrapper/instance-patcher.ts +0 -288
  213. package/src/core/wrapper/request-handler.ts +0 -423
  214. package/src/core/wrapper/stream-wrapper.ts +0 -100
  215. package/src/index.ts +0 -360
  216. package/src/types/function-parameters.ts +0 -251
  217. package/src/types/index.ts +0 -310
  218. package/src/types/openai-augmentation.ts +0 -232
  219. package/src/types/responses-api.ts +0 -308
  220. package/src/utils/azure-model-resolver.ts +0 -220
  221. package/src/utils/constants.ts +0 -21
  222. package/src/utils/error-handler.ts +0 -251
  223. package/src/utils/metadata-builder.ts +0 -228
  224. package/src/utils/provider-detection.ts +0 -257
  225. package/src/utils/request-handler-factory.ts +0 -285
  226. package/src/utils/stop-reason-mapper.ts +0 -78
  227. package/src/utils/type-guards.ts +0 -202
  228. package/src/utils/url-builder.ts +0 -68
@@ -1,251 +0,0 @@
1
- /**
2
- * Error Handler Utilities
3
- *
4
- * Centralized error handling patterns to eliminate repetitive try/catch blocks
5
- * and provide consistent error logging and recovery strategies.
6
- */
7
-
8
- import { Logger } from '../types/index.js';
9
- import { ERROR_MESSAGE_PATTERNS_TYPE_CONFIG, MESSAGE_PATTERNS_TYPE_NETWORK } from './constants.js';
10
-
11
- /**
12
- * Error handling strategy configuration
13
- */
14
- export interface ErrorHandlingStrategy {
15
- /** Whether to log the error */
16
- logError?: boolean;
17
- /** Whether to re-throw the error */
18
- rethrow?: boolean;
19
- /** Custom error message prefix */
20
- messagePrefix?: string;
21
- /** Fallback value to return on error */
22
- fallbackValue?: unknown;
23
- /** Custom error transformation function */
24
- transformError?: (error: unknown) => Error;
25
- }
26
-
27
- /**
28
- * Default error handling strategy
29
- */
30
- const DEFAULT_STRATEGY: Required<ErrorHandlingStrategy> = {
31
- logError: true,
32
- rethrow: true,
33
- messagePrefix: '',
34
- fallbackValue: undefined,
35
- transformError: (error: unknown) => (error instanceof Error ? error : new Error(String(error))),
36
- };
37
-
38
- /**
39
- * Safe async operation wrapper with comprehensive error handling
40
- *
41
- * @param operation - The async operation to execute
42
- * @param context - Context information for logging
43
- * @param strategy - Error handling strategy
44
- * @param logger - Logger instance
45
- * @returns Promise with result or fallback value
46
- */
47
- export async function safeAsyncOperation<T>(
48
- operation: () => Promise<T>,
49
- context: string,
50
- strategy: ErrorHandlingStrategy = {},
51
- logger?: Logger
52
- ): Promise<T | undefined> {
53
- const config = { ...DEFAULT_STRATEGY, ...strategy };
54
-
55
- try {
56
- return await operation();
57
- } catch (error) {
58
- const transformedError = config.transformError(error);
59
-
60
- if (config.logError && logger) {
61
- logger.error(`${config.messagePrefix}${context}`, {
62
- error: transformedError.message,
63
- stack: transformedError.stack,
64
- });
65
- }
66
-
67
- if (config.rethrow) throw transformedError;
68
- return config.fallbackValue as T | undefined;
69
- }
70
- }
71
-
72
- /**
73
- * Safe sync operation wrapper
74
- *
75
- * @param operation - The sync operation to execute
76
- * @param context - Context information for logging
77
- * @param strategy - Error handling strategy
78
- * @param logger - Logger instance
79
- * @returns Result or fallback value
80
- */
81
- export function safeSyncOperation<T>(
82
- operation: () => T,
83
- context: string,
84
- strategy: ErrorHandlingStrategy = {},
85
- logger?: Logger
86
- ): T | undefined {
87
- const config = { ...DEFAULT_STRATEGY, ...strategy };
88
-
89
- try {
90
- return operation();
91
- } catch (error) {
92
- const transformedError = config.transformError(error);
93
-
94
- if (config.logError && logger) {
95
- logger.error(`${config.messagePrefix}${context}`, {
96
- error: transformedError.message,
97
- stack: transformedError.stack,
98
- });
99
- }
100
-
101
- if (config.rethrow) throw transformedError;
102
- return config.fallbackValue as T | undefined;
103
- }
104
- }
105
-
106
- /**
107
- * Validation wrapper that provides clear error messages
108
- *
109
- * @param value - Value to validate
110
- * @param validator - Validation function
111
- * @param errorMessage - Error message if validation fails
112
- * @returns Validated value
113
- */
114
- export function validateOrThrow<T>(
115
- value: unknown,
116
- validator: (value: unknown) => value is T,
117
- errorMessage: string
118
- ): T {
119
- if (!validator(value)) throw new Error(errorMessage);
120
- return value;
121
- }
122
-
123
- /**
124
- * Validation wrapper that returns undefined on failure
125
- *
126
- * @param value - Value to validate
127
- * @param validator - Validation function
128
- * @param logger - Optional logger for warnings
129
- * @param context - Context for logging
130
- * @returns Validated value or undefined
131
- */
132
- export function validateOrUndefined<T>(
133
- value: unknown,
134
- validator: (value: unknown) => value is T,
135
- logger?: Logger,
136
- context?: string
137
- ): T | undefined {
138
- if (!validator(value)) {
139
- if (logger && context) {
140
- logger.warn(`Validation failed: ${context}`, { value });
141
- }
142
- return;
143
- }
144
- return value;
145
- }
146
-
147
- /**
148
- * Create a retry wrapper for operations that might fail temporarily
149
- *
150
- * @param operation - Operation to retry
151
- * @param maxRetries - Maximum number of retries
152
- * @param delayMs - Delay between retries in milliseconds
153
- * @param logger - Logger for retry attempts
154
- * @returns Promise with operation result
155
- */
156
- export async function withRetry<T>(
157
- operation: () => Promise<T>,
158
- maxRetries: number = 3,
159
- delayMs: number = 1000,
160
- logger?: Logger
161
- ): Promise<T> {
162
- let lastError: Error;
163
- for (let attempt = 1; attempt <= maxRetries; attempt++) {
164
- try {
165
- return await operation();
166
- } catch (error) {
167
- lastError = error instanceof Error ? error : new Error(String(error));
168
-
169
- if (attempt === maxRetries) {
170
- break;
171
- }
172
-
173
- if (logger) {
174
- logger.warn(`Operation failed, retrying (${attempt}/${maxRetries})`, {
175
- error: lastError.message,
176
- nextRetryIn: delayMs,
177
- });
178
- }
179
- await new Promise(resolve => setTimeout(resolve, delayMs));
180
- }
181
- }
182
-
183
- // eslint-disable-next-line no-throw-literal
184
- throw lastError!;
185
- }
186
-
187
- /**
188
- * Common error types for better error handling
189
- */
190
- export class ValidationError extends Error {
191
- constructor(
192
- message: string,
193
- public readonly context?: Record<string, unknown>
194
- ) {
195
- super(message);
196
- this.name = 'ValidationError';
197
- }
198
- }
199
-
200
- export class ConfigurationError extends Error {
201
- constructor(
202
- message: string,
203
- public readonly context?: Record<string, unknown>
204
- ) {
205
- super(message);
206
- this.name = 'ConfigurationError';
207
- }
208
- }
209
-
210
- export class NetworkError extends Error {
211
- constructor(
212
- message: string,
213
- public readonly context?: Record<string, unknown>
214
- ) {
215
- super(message);
216
- this.name = 'NetworkError';
217
- }
218
- }
219
-
220
- /**
221
- * Error classification utility
222
- */
223
- export function classifyError(error: unknown): {
224
- type: 'validation' | 'configuration' | 'network' | 'unknown';
225
- message: string;
226
- isRetryable: boolean;
227
- } {
228
- if (error instanceof ValidationError) {
229
- return { type: 'validation', message: error.message, isRetryable: false };
230
- }
231
-
232
- if (error instanceof ConfigurationError) {
233
- return { type: 'configuration', message: error.message, isRetryable: false };
234
- }
235
-
236
- if (error instanceof NetworkError) {
237
- return { type: 'network', message: error.message, isRetryable: true };
238
- }
239
-
240
- const message = error instanceof Error ? error.message : String(error);
241
-
242
- // Classify based on message patterns
243
- if (MESSAGE_PATTERNS_TYPE_NETWORK.some(pattern => message.includes(pattern))) {
244
- return { type: 'network', message, isRetryable: true };
245
- }
246
-
247
- if (ERROR_MESSAGE_PATTERNS_TYPE_CONFIG.some(pattern => message.includes(pattern))) {
248
- return { type: 'configuration', message, isRetryable: false };
249
- }
250
- return { type: 'unknown', message, isRetryable: false };
251
- }
@@ -1,228 +0,0 @@
1
- /**
2
- * Metadata Builder Utilities
3
- *
4
- * Centralized metadata handling to eliminate repetitive spreading
5
- * and provide consistent metadata processing across the codebase.
6
- */
7
-
8
- import { UsageMetadata, Subscriber } from '../types/index.js';
9
-
10
- /**
11
- * Metadata field configuration for conditional inclusion
12
- */
13
- interface MetadataFieldConfig {
14
- /** Source field name in UsageMetadata */
15
- source: keyof UsageMetadata;
16
- /** Target field name in payload (defaults to source) */
17
- target?: string;
18
- /** Whether this field is required */
19
- required?: boolean;
20
- /** Custom transformation function */
21
- transform?: (value: unknown) => unknown;
22
- }
23
-
24
- /**
25
- * Metadata mapping configuration
26
- * Maps UsageMetadata fields to payload fields with optional transformations
27
- * Subscriber object is passed through directly without transformation
28
- */
29
- const METADATA_FIELD_MAP: MetadataFieldConfig[] = [
30
- { source: 'traceId' },
31
- { source: 'taskType' },
32
- { source: 'agent' },
33
- { source: 'organizationId' },
34
- { source: 'productId' },
35
- { source: 'subscriber' }, // Pass through nested subscriber object directly
36
- { source: 'subscriptionId' },
37
- {
38
- source: 'responseQualityScore',
39
- transform: (value: unknown) => {
40
- // Ensure quality score is between 0.0 and 1.0 (API spec requirement)
41
- if (typeof value === 'number') return Math.max(0, Math.min(1, value));
42
- return value;
43
- },
44
- },
45
- ];
46
-
47
- /**
48
- * Build metadata object for payload inclusion
49
- *
50
- * This function eliminates the repetitive spreading pattern and provides
51
- * a clean, testable way to handle metadata transformation.
52
- * Subscriber object is passed through directly without transformation.
53
- *
54
- * @param usageMetadata - Source metadata from request
55
- * @returns Clean metadata object for payload
56
- */
57
- export function buildMetadataFields(usageMetadata?: UsageMetadata): Record<string, unknown> {
58
- if (!usageMetadata) return {};
59
- const result: Record<string, unknown> = {};
60
-
61
- // Process all metadata fields including nested subscriber object
62
- for (const config of METADATA_FIELD_MAP) {
63
- const value = usageMetadata[config.source];
64
-
65
- // Skip undefined values (but allow null, empty strings, objects, etc.)
66
- if (value === undefined) continue;
67
-
68
- // Apply transformation if configured
69
- const transformedValue = config.transform ? config.transform(value) : value;
70
-
71
- // Use target field name or default to source
72
- const targetField = config.target || config.source;
73
-
74
- result[targetField] = transformedValue;
75
- }
76
- return result;
77
- }
78
-
79
- /**
80
- * Validate metadata completeness for specific use cases
81
- *
82
- * @param usageMetadata - Metadata to validate
83
- * @param requiredFields - List of required field names
84
- * @returns Validation result
85
- */
86
- export function validateMetadata(
87
- usageMetadata?: UsageMetadata,
88
- requiredFields: (keyof UsageMetadata)[] = []
89
- ): {
90
- isValid: boolean;
91
- missingFields: string[];
92
- warnings: string[];
93
- } {
94
- const missingFields: string[] = [];
95
- const warnings: string[] = [];
96
-
97
- if (!usageMetadata && requiredFields.length > 0) {
98
- return {
99
- isValid: false,
100
- missingFields: requiredFields as string[],
101
- warnings: ['No metadata provided'],
102
- };
103
- }
104
-
105
- if (usageMetadata) {
106
- // Check required fields
107
- for (const field of requiredFields) {
108
- if (!usageMetadata[field]) missingFields.push(String(field));
109
- }
110
-
111
- // Check for common issues
112
- if (usageMetadata.responseQualityScore) {
113
- const score = usageMetadata.responseQualityScore;
114
- // API Spec: https://revenium.readme.io/reference/meter_ai_completion (responseQualityScore)
115
- // "typically on a 0.0-1.0 scale"
116
- if (typeof score !== 'number' || score < 0 || score > 1) {
117
- warnings.push('responseQualityScore should be a number between 0.0 and 1.0');
118
- }
119
- }
120
-
121
- if (usageMetadata.subscriber?.email && !usageMetadata.subscriber.email.includes('@')) {
122
- warnings.push('subscriber.email does not appear to be a valid email address');
123
- }
124
- }
125
- return {
126
- isValid: missingFields.length === 0,
127
- missingFields,
128
- warnings,
129
- };
130
- }
131
-
132
- /**
133
- * Merge multiple metadata sources with priority
134
- *
135
- * @param sources - Metadata sources in priority order (first wins)
136
- * @returns Merged metadata object
137
- */
138
- export function mergeMetadata(...sources: (UsageMetadata | undefined)[]): UsageMetadata {
139
- const result: UsageMetadata = {};
140
-
141
- // Process sources in reverse order so first source wins
142
- for (const source of sources.reverse()) {
143
- if (source) Object.assign(result, source);
144
- }
145
- return result;
146
- }
147
-
148
- /**
149
- * Extract metadata from request parameters safely
150
- *
151
- * @param params - Request parameters that might contain usageMetadata
152
- * @returns Extracted metadata and cleaned parameters
153
- */
154
- export function extractMetadata<T extends Record<string, unknown>>(
155
- params: T & { usageMetadata?: UsageMetadata }
156
- ): {
157
- metadata: UsageMetadata | undefined;
158
- cleanParams: Omit<T, 'usageMetadata'>;
159
- } {
160
- const { usageMetadata, ...cleanParams } = params;
161
- return {
162
- metadata: usageMetadata,
163
- cleanParams: cleanParams as Omit<T, 'usageMetadata'>,
164
- };
165
- }
166
-
167
- /**
168
- * Create a metadata context for consistent logging
169
- * Uses sanitization to protect PII (emails are masked)
170
- *
171
- * @param usageMetadata - Source metadata
172
- * @returns Logging context object with sanitized PII
173
- */
174
- export function createLoggingContext(usageMetadata?: UsageMetadata): Record<string, unknown> {
175
- if (!usageMetadata) return {};
176
-
177
- // Use sanitizer to protect PII in logs
178
- const sanitized = sanitizeMetadataForLogging(usageMetadata);
179
- const sanitizedSubscriber = sanitized.subscriber as { id?: string; email?: string; credential?: unknown };
180
-
181
- return {
182
- traceId: usageMetadata.traceId,
183
- taskType: usageMetadata.taskType,
184
- subscriberId: usageMetadata.subscriber?.id,
185
- subscriberEmail: sanitizedSubscriber?.email, // ← Now masked: us***@example.com
186
- organizationId: usageMetadata.organizationId,
187
- productId: usageMetadata.productId,
188
- agent: usageMetadata.agent,
189
- };
190
- }
191
-
192
- /**
193
- * Sanitize metadata for logging (remove sensitive fields)
194
- *
195
- * @param usageMetadata - Source metadata
196
- * @returns Sanitized metadata safe for logging
197
- */
198
- export function sanitizeMetadataForLogging(usageMetadata?: UsageMetadata): Record<string, unknown> {
199
- if (!usageMetadata) return {};
200
-
201
- // Create a copy and handle nested subscriber object
202
- const { subscriber, ...safeMetadata } = usageMetadata;
203
-
204
- const result: Record<string, unknown> = { ...safeMetadata };
205
-
206
- // Sanitize subscriber object if present
207
- if (subscriber) {
208
- const sanitizedSubscriber: Record<string, unknown> = {};
209
-
210
- if (subscriber.id) {
211
- sanitizedSubscriber.id = subscriber.id;
212
- }
213
-
214
- if (subscriber.email) {
215
- // Mask email: handles single-char emails (a@x.com → a***@x.com)
216
- sanitizedSubscriber.email = subscriber.email.replace(/(.{1,2}).*(@.*)/, '$1***$2');
217
- }
218
-
219
- if (subscriber.credential) {
220
- sanitizedSubscriber.credential = {
221
- name: subscriber.credential.name,
222
- value: '[REDACTED]',
223
- };
224
- }
225
- result.subscriber = sanitizedSubscriber;
226
- }
227
- return result;
228
- }