@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,137 +0,0 @@
1
- /**
2
- * Payload Builder Module
3
- *
4
- * Handles construction of Revenium API payloads.
5
- * Extracted from tracking.ts for single responsibility.
6
- */
7
-
8
- import { randomUUID } from 'crypto';
9
- import { ReveniumPayload, ProviderInfo } from '../../types/index.js';
10
- import {
11
- OpenAIChatResponse,
12
- OpenAIEmbeddingResponse,
13
- OpenAIChatRequest,
14
- OpenAIEmbeddingRequest,
15
- } from '../../types/function-parameters.js';
16
- import { getLogger } from '../config/index.js';
17
- import { mapStopReason } from '../../utils/stop-reason-mapper.js';
18
- import { buildMetadataFields } from '../../utils/metadata-builder.js';
19
- import { resolveAzureModelName } from '../../utils/azure-model-resolver.js';
20
- import { getProviderMetadata } from '../providers/index.js';
21
-
22
- // Global logger
23
- const logger = getLogger();
24
-
25
- /**
26
- * Build payload for Revenium API
27
- *
28
- * This shared payload builder eliminates payload duplication between
29
- * chat completions and embeddings. Handles both CHAT and EMBED operation types.
30
- *
31
- * @param operationType - Type of operation (CHAT or EMBED)
32
- * @param response - API response from OpenAI/Azure
33
- * @param request - Original request parameters
34
- * @param startTime - Request start timestamp
35
- * @param duration - Request duration in milliseconds
36
- * @param providerInfo - Provider information for Azure support
37
- * @returns Constructed payload for Revenium API
38
- */
39
- export function buildPayload(
40
- operationType: 'CHAT' | 'EMBED',
41
- response: OpenAIChatResponse | OpenAIEmbeddingResponse,
42
- request: OpenAIChatRequest | OpenAIEmbeddingRequest,
43
- startTime: number,
44
- duration: number,
45
- providerInfo?: ProviderInfo
46
- ): ReveniumPayload {
47
- const now = new Date().toISOString();
48
- const requestTime = new Date(startTime).toISOString();
49
- const usage = response.usage;
50
-
51
- // Resolve model name for Azure deployments
52
- const originalModel = response.model;
53
- const resolvedModel = providerInfo?.isAzure
54
- ? resolveAzureModelName(originalModel)
55
- : originalModel;
56
-
57
- // Get provider metadata (fallback: OpenAI direct)
58
- const providerMetadata = providerInfo
59
- ? getProviderMetadata(providerInfo)
60
- : { provider: 'OpenAI', modelSource: 'OPENAI' };
61
-
62
- if (providerInfo?.isAzure && resolvedModel !== originalModel) {
63
- logger.debug('Azure model name resolved for pricing', {
64
- deployment: originalModel,
65
- resolved: resolvedModel,
66
- provider: providerMetadata.provider,
67
- });
68
- }
69
-
70
- // Build metadata fields using utility (eliminates repetitive spreading)
71
- const metadataFields = buildMetadataFields(request.usageMetadata);
72
-
73
- // Common fields for all operations
74
- const commonPayload = {
75
- costType: 'AI' as const,
76
- model: resolvedModel, // Use resolved model name for accurate pricing
77
- responseTime: now,
78
- requestDuration: duration,
79
- provider: providerMetadata.provider,
80
- modelSource: providerMetadata.modelSource,
81
- requestTime,
82
- completionStartTime: now,
83
-
84
- // Common token counts
85
- inputTokenCount: usage.prompt_tokens,
86
- totalTokenCount: usage.total_tokens,
87
-
88
- // Metadata fields (processed by utility)
89
- ...metadataFields,
90
-
91
- // Fixed middleware source identifier (spec format: revenium-{provider}-{language})
92
- middlewareSource: 'revenium-openai-node',
93
-
94
- // Backend calculates costs
95
- inputTokenCost: undefined,
96
- outputTokenCost: undefined,
97
- totalCost: undefined,
98
- };
99
-
100
- // Operation-specific fields
101
-
102
- if (operationType !== 'CHAT') {
103
- // For embeddings, we don't need the response cast since we use commonPayload
104
- return {
105
- ...commonPayload,
106
- operationType: 'EMBED',
107
- transactionId: `embed-${randomUUID()}`,
108
- outputTokenCount: 0,
109
- // Embeddings don't support reasoning or caching
110
- reasoningTokenCount: undefined,
111
- cacheCreationTokenCount: undefined,
112
- cacheReadTokenCount: undefined,
113
- stopReason: 'END',
114
- isStreamed: false,
115
- timeToFirstToken: undefined, // Not applicable for embeddings
116
- };
117
- }
118
- const chatResponse = response as OpenAIChatResponse;
119
- const chatUsage = chatResponse.usage;
120
-
121
- return {
122
- ...commonPayload,
123
- operationType: 'CHAT',
124
- transactionId: chatResponse.id || `chat-${randomUUID()}`,
125
- outputTokenCount: chatUsage.completion_tokens || 0,
126
- // Leave null for models without reasoning capabilities (API spec)
127
- reasoningTokenCount: chatUsage.reasoning_tokens ?? undefined,
128
- // OpenAI doesn't report cache creation tokens
129
- cacheCreationTokenCount: undefined,
130
- // Only include if provider reports cache hits
131
- cacheReadTokenCount: chatUsage.cached_tokens ?? undefined,
132
- stopReason: mapStopReason(chatResponse.choices?.[0]?.finish_reason, logger),
133
- isStreamed: Boolean((request as OpenAIChatRequest).stream),
134
- // TODO: Implement real TTFB tracking for streaming requests
135
- timeToFirstToken: undefined,
136
- };
137
- }
@@ -1,189 +0,0 @@
1
- /**
2
- * Usage Tracker Module
3
- *
4
- * High-level tracking functions that combine payload building and API communication.
5
- * Extracted from tracking.ts for better organization.
6
- */
7
-
8
- import { randomUUID } from 'crypto';
9
- import { UsageMetadata, ProviderInfo } from '../../types/index.js';
10
- import {
11
- OpenAIChatResponse,
12
- OpenAIEmbeddingResponse,
13
- OpenAIChatRequest,
14
- OpenAIEmbeddingRequest,
15
- } from '../../types/function-parameters.js';
16
- import { getLogger } from '../config/index.js';
17
- import { sendToRevenium } from './api-client.js';
18
- import { buildPayload } from './payload-builder.js';
19
- import { safeAsyncOperation } from '../../utils/error-handler.js';
20
-
21
- // Global logger
22
- const logger = getLogger();
23
-
24
- /**
25
- * Chat completions tracking - now a thin wrapper with Azure support
26
- */
27
- export async function sendReveniumMetrics(
28
- response: OpenAIChatResponse,
29
- request: OpenAIChatRequest,
30
- startTime: number,
31
- duration: number,
32
- providerInfo?: ProviderInfo
33
- ): Promise<void> {
34
- await safeAsyncOperation(
35
- async () => {
36
- const payload = buildPayload('CHAT', response, request, startTime, duration, providerInfo);
37
- await sendToRevenium(payload);
38
- },
39
- 'Chat completion tracking',
40
- {
41
- logError: true,
42
- rethrow: false, // Don't rethrow to maintain fire-and-forget behavior
43
- messagePrefix: 'Chat completion tracking failed: ',
44
- },
45
- logger
46
- );
47
- }
48
-
49
- /**
50
- * Embeddings tracking - now a thin wrapper with Azure support
51
- */
52
- export async function sendReveniumEmbeddingsMetrics(
53
- response: OpenAIEmbeddingResponse,
54
- request: OpenAIEmbeddingRequest,
55
- startTime: number,
56
- duration: number,
57
- providerInfo?: ProviderInfo
58
- ): Promise<void> {
59
- await safeAsyncOperation(
60
- async () => {
61
- const payload = buildPayload('EMBED', response, request, startTime, duration, providerInfo);
62
- await sendToRevenium(payload);
63
- },
64
- 'Embeddings tracking',
65
- {
66
- logError: true,
67
- rethrow: false, // Don't rethrow to maintain fire-and-forget behavior
68
- messagePrefix: 'Embeddings tracking failed: ',
69
- },
70
- logger
71
- );
72
- }
73
-
74
- /**
75
- * Fire-and-forget wrapper for chat completions with Azure support
76
- */
77
- export function trackUsageAsync(trackingData: {
78
- requestId: string;
79
- model: string;
80
- promptTokens: number;
81
- completionTokens: number;
82
- totalTokens: number;
83
- reasoningTokens?: number;
84
- cachedTokens?: number;
85
- duration: number;
86
- finishReason: string | null;
87
- usageMetadata?: UsageMetadata;
88
- isStreamed?: boolean;
89
- timeToFirstToken?: number;
90
- providerInfo?: ProviderInfo;
91
- }): void {
92
- const mockResponse = {
93
- id: trackingData.requestId,
94
- model: trackingData.model,
95
- usage: {
96
- prompt_tokens: trackingData.promptTokens,
97
- completion_tokens: trackingData.completionTokens,
98
- total_tokens: trackingData.totalTokens,
99
- ...(trackingData.reasoningTokens && { reasoning_tokens: trackingData.reasoningTokens }),
100
- ...(trackingData.cachedTokens && { cached_tokens: trackingData.cachedTokens }),
101
- },
102
- choices: [
103
- {
104
- finish_reason: trackingData.finishReason,
105
- },
106
- ],
107
- };
108
-
109
- const mockRequest: OpenAIChatRequest = {
110
- model: trackingData.model,
111
- messages: [], // Mock empty messages array for type compliance
112
- usageMetadata: trackingData.usageMetadata,
113
- stream: trackingData.isStreamed,
114
- };
115
-
116
- const startTime = Date.now() - trackingData.duration;
117
-
118
- sendReveniumMetrics(
119
- mockResponse,
120
- mockRequest,
121
- startTime,
122
- trackingData.duration,
123
- trackingData.providerInfo
124
- )
125
- .then(() => {
126
- logger.debug('Usage tracking completed successfully', {
127
- requestId: trackingData.requestId,
128
- model: trackingData.model,
129
- totalTokens: trackingData.totalTokens,
130
- isStreamed: trackingData.isStreamed,
131
- });
132
- })
133
- .catch(error => {
134
- logger.warn('Usage tracking failed', {
135
- error: error instanceof Error ? error.message : String(error),
136
- requestId: trackingData.requestId,
137
- model: trackingData.model,
138
- });
139
- });
140
- }
141
-
142
- /**
143
- * Fire-and-forget wrapper for embeddings with Azure support
144
- */
145
- export function trackEmbeddingsUsageAsync(trackingData: {
146
- transactionId: string;
147
- model: string;
148
- promptTokens: number;
149
- totalTokens: number;
150
- duration: number;
151
- usageMetadata?: UsageMetadata;
152
- requestStartTime: number;
153
- providerInfo?: ProviderInfo;
154
- }): void {
155
- const mockResponse: OpenAIEmbeddingResponse = {
156
- model: trackingData.model,
157
- usage: {
158
- prompt_tokens: trackingData.promptTokens,
159
- total_tokens: trackingData.totalTokens,
160
- },
161
- data: [], // Mock empty data array for type compliance
162
- object: 'list',
163
- };
164
-
165
- const mockRequest: OpenAIEmbeddingRequest = {
166
- model: trackingData.model,
167
- input: '', // Mock empty input for type compliance
168
- usageMetadata: trackingData.usageMetadata,
169
- };
170
-
171
- sendReveniumEmbeddingsMetrics(
172
- mockResponse,
173
- mockRequest,
174
- trackingData.requestStartTime,
175
- trackingData.duration,
176
- trackingData.providerInfo
177
- )
178
- .then(() => {
179
- logger.debug('Embeddings tracking completed successfully', {
180
- transactionId: trackingData.transactionId,
181
- });
182
- })
183
- .catch(error => {
184
- logger.warn('Embeddings tracking failed', {
185
- error: error instanceof Error ? error.message : String(error),
186
- transactionId: trackingData.transactionId,
187
- });
188
- });
189
- }
@@ -1,9 +0,0 @@
1
- /**
2
- * Wrapper module - Main exports
3
- *
4
- * This module provides a clean interface for OpenAI client wrapping,
5
- * separating concerns into focused sub-modules.
6
- */
7
-
8
- // Re-export all wrapper functionality
9
- export { patchOpenAI, patchOpenAIInstance, getProviderInfo } from './instance-patcher.js';
@@ -1,288 +0,0 @@
1
- /**
2
- * Instance Patcher Module
3
- *
4
- * Handles patching of OpenAI client instances.
5
- * Extracted from wrapper.ts for better organization.
6
- */
7
-
8
- import OpenAI from 'openai';
9
- import type { ChatCompletionCreateParams } from 'openai/resources/chat/completions';
10
- import type { EmbeddingCreateParams } from 'openai/resources/embeddings';
11
- import { ProviderInfo } from '../../types/index.js';
12
- import {
13
- OpenAIClientInstance,
14
- OpenAIChatRequest,
15
- OpenAIEmbeddingRequest,
16
- OpenAIRequestOptions,
17
- OpenAIResponsesOriginalFunction,
18
- } from '../../types/function-parameters.js';
19
- import { isOpenAIClientInstance } from '../../utils/type-guards.js';
20
- import { extractMetadata, createLoggingContext } from '../../utils/metadata-builder.js';
21
- import {
22
- requestHandlerFactory,
23
- ensureFactoryInitialized,
24
- } from '../../utils/request-handler-factory.js';
25
- import { getLogger, getConfig } from '../config/index.js';
26
- import { detectProvider } from '../providers/index.js';
27
- import { OpenAIResponsesRequest } from '../../types/responses-api.js';
28
-
29
- // Import the type augmentations to ensure they're available
30
- import '../../types/openai-augmentation.js';
31
-
32
- // Interface for OpenAI instance with Responses API
33
- interface OpenAIWithResponses extends OpenAIClientInstance {
34
- responses?: {
35
- create: (params: OpenAIResponsesRequest, options?: OpenAIRequestOptions) => Promise<unknown>;
36
- };
37
- }
38
-
39
- /**
40
- * Set to track patched instances
41
- */
42
- const patchedInstances = new WeakSet();
43
-
44
- /**
45
- * WeakMap to store provider information for each client instance
46
- */
47
- const instanceProviders = new WeakMap<OpenAIClientInstance, ProviderInfo>();
48
-
49
- // Global logger
50
- const logger = getLogger();
51
-
52
- /**
53
- * Get provider information for a client instance
54
- */
55
- export function getProviderInfo(instance: OpenAIClientInstance): ProviderInfo | undefined {
56
- return instanceProviders.get(instance);
57
- }
58
-
59
- /**
60
- * Simple approach: Only patch instances when users create them
61
- * No global patching, no dummy keys - just clean instance patching
62
- */
63
- export function patchOpenAI(): void {
64
- logger.info('Revenium OpenAI middleware loaded and ready');
65
- logger.debug('Use patchOpenAIInstance() to patch specific OpenAI instances');
66
- }
67
-
68
- /**
69
- * Manually patch an existing OpenAI instance
70
- * This is the main function users should call
71
- */
72
- export function patchOpenAIInstance(instance: OpenAI): OpenAI {
73
- // Check if middleware is initialized
74
- const config = getConfig();
75
- if (!config) {
76
- logger.warn('Revenium middleware not initialized.');
77
- logger.warn(
78
- 'Auto-initialization may have failed. Try calling initializeReveniumFromEnv() explicitly.'
79
- );
80
- logger.warn('Check that REVENIUM_METERING_API_KEY environment variable is set.');
81
- logger.warn(
82
- 'OpenAI instance will be patched but tracking may not work without proper configuration.'
83
- );
84
- } else {
85
- logger.debug('Revenium middleware is properly configured');
86
- }
87
-
88
- if (patchedInstances.has(instance)) {
89
- logger.debug('OpenAI instance already patched, skipping');
90
- return instance;
91
- }
92
-
93
- patchInstance(instance as unknown as OpenAIClientInstance);
94
- logger.debug('OpenAI instance patched successfully');
95
-
96
- return instance;
97
- }
98
-
99
- /**
100
- * Patch an individual OpenAI instance
101
- */
102
- function patchInstance(instance: OpenAIClientInstance): void {
103
- try {
104
- // Validate instance
105
- if (!isOpenAIClientInstance(instance)) {
106
- logger.error('Invalid OpenAI client instance provided to patchInstance');
107
- return;
108
- }
109
-
110
- // Detect provider type for this instance
111
- const providerInfo = detectProvider(instance);
112
- instanceProviders.set(instance, providerInfo);
113
-
114
- logger.debug('Provider detection completed for instance', {
115
- provider: providerInfo.provider,
116
- isAzure: providerInfo.isAzure,
117
- hasAzureConfig: !!providerInfo.azureConfig,
118
- });
119
-
120
- // Patch chat completions
121
- patchChatCompletions(instance);
122
-
123
- // Patch embeddings
124
- patchEmbeddings(instance);
125
-
126
- // Patch responses API (new OpenAI Responses API)
127
- patchResponses(instance);
128
-
129
- // Mark as patched
130
- patchedInstances.add(instance);
131
- } catch (error) {
132
- logger.error('Failed to patch OpenAI instance', {
133
- error: error instanceof Error ? error.message : String(error),
134
- });
135
- }
136
- }
137
-
138
- /**
139
- * Patch chat completions endpoint
140
- */
141
- function patchChatCompletions(instance: OpenAIClientInstance): void {
142
- if (!instance.chat || !instance.chat.completions || !instance.chat.completions.create) {
143
- return logger.warn('OpenAI instance missing chat.completions.create, skipping chat patch');
144
- }
145
-
146
- // Store the original create method
147
- const originalCreate = instance.chat.completions.create.bind(instance.chat.completions);
148
-
149
- // Replace the create method with our wrapped version
150
- instance.chat.completions.create = async function (
151
- params: ChatCompletionCreateParams,
152
- options?: OpenAIRequestOptions
153
- ) {
154
- // Extract metadata using utility
155
- const { metadata, cleanParams } = extractMetadata(params as OpenAIChatRequest);
156
- const typedParams = params as OpenAIChatRequest;
157
-
158
- logger.debug('OpenAI chat.completions.create intercepted', {
159
- ...createLoggingContext(metadata),
160
- model: typedParams.model,
161
- stream: !!typedParams.stream,
162
- });
163
-
164
- // Record request start time
165
- const requestStartTime = Date.now();
166
-
167
- // Ensure factory is initialized and route request
168
- try {
169
- await ensureFactoryInitialized();
170
- } catch (error) {
171
- logger.error('Failed to initialize request handler factory', { error });
172
- throw new Error('Middleware initialization failed - cannot process request');
173
- }
174
- return requestHandlerFactory.routeChatRequest(
175
- originalCreate,
176
- cleanParams,
177
- options,
178
- metadata,
179
- requestStartTime,
180
- instance
181
- );
182
- };
183
- }
184
-
185
- /**
186
- * Patch embeddings endpoint
187
- */
188
- function patchEmbeddings(instance: OpenAIClientInstance): void {
189
- if (!instance.embeddings || !instance.embeddings.create) {
190
- return logger.warn('OpenAI instance missing embeddings.create, skipping embeddings patch');
191
- }
192
- // Store the original embeddings create method
193
- const originalEmbeddingsCreate = instance.embeddings.create.bind(instance.embeddings);
194
-
195
- // Replace the embeddings create method with our wrapped version
196
- instance.embeddings.create = async function (
197
- params: EmbeddingCreateParams,
198
- options?: OpenAIRequestOptions
199
- ) {
200
- // Extract metadata using utility
201
- const { metadata, cleanParams } = extractMetadata(params as OpenAIEmbeddingRequest);
202
- const typedParams = params as OpenAIEmbeddingRequest;
203
-
204
- logger.debug('OpenAI embeddings.create intercepted', {
205
- ...createLoggingContext(metadata),
206
- model: typedParams.model,
207
- inputType: typeof typedParams.input,
208
- });
209
-
210
- // Record request start time
211
- const requestStartTime = Date.now();
212
-
213
- // Ensure factory is initialized and route request
214
- try {
215
- await ensureFactoryInitialized();
216
- } catch (error) {
217
- logger.error('Failed to initialize request handler factory', { error });
218
- throw new Error('Middleware initialization failed - cannot process request');
219
- }
220
- return requestHandlerFactory.routeEmbeddingsRequest(
221
- originalEmbeddingsCreate,
222
- cleanParams,
223
- options,
224
- metadata,
225
- requestStartTime,
226
- instance
227
- );
228
- };
229
- }
230
-
231
- /**
232
- * Patch responses endpoint (new OpenAI Responses API)
233
- */
234
- function patchResponses(instance: OpenAIClientInstance): void {
235
- // Type assertion for new Responses API (not yet in OpenAI types)
236
- const responsesAPI = instance as OpenAIWithResponses;
237
-
238
- // Check if the instance has the responses API (it's a newer feature)
239
- if (!responsesAPI.responses || !responsesAPI.responses.create) {
240
- logger.debug(
241
- 'OpenAI instance missing responses.create, skipping responses patch (this is normal for older SDK versions)'
242
- );
243
- return;
244
- }
245
-
246
- // Store the original responses create method
247
- const originalResponsesCreate = responsesAPI.responses.create.bind(responsesAPI.responses);
248
-
249
- // Replace the responses create method with our wrapped version
250
- responsesAPI.responses.create = async function (
251
- params: OpenAIResponsesRequest,
252
- options?: OpenAIRequestOptions
253
- ) {
254
- // Extract metadata using utility (similar to chat completions)
255
- const { metadata, cleanParams } = extractMetadata(params);
256
-
257
- logger.debug('OpenAI responses.create intercepted', {
258
- ...createLoggingContext(metadata),
259
- model: params.model,
260
- stream: !!params.stream,
261
- inputType: typeof params.input,
262
- });
263
-
264
- // Record request start time
265
- const requestStartTime = Date.now();
266
-
267
- // Ensure factory is initialized and route request
268
- try {
269
- await ensureFactoryInitialized();
270
- } catch (error) {
271
- logger.error('Failed to initialize request handler factory', { error });
272
- throw new Error('Middleware initialization failed - cannot process request');
273
- }
274
- return requestHandlerFactory.routeResponsesRequest(
275
- originalResponsesCreate as OpenAIResponsesOriginalFunction,
276
- cleanParams,
277
- options,
278
- metadata,
279
- requestStartTime,
280
- instance
281
- );
282
- };
283
- }
284
-
285
- /**
286
- * Export instance providers for request handlers
287
- */
288
- export { instanceProviders };