@revenium/perplexity 1.0.0

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 (234) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +367 -0
  3. package/dist/index.d.ts +8 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +40 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/models/Exception/ApiResponseError.d.ts +6 -0
  8. package/dist/models/Exception/ApiResponseError.d.ts.map +1 -0
  9. package/dist/models/Exception/ApiResponseError.js +13 -0
  10. package/dist/models/Exception/ApiResponseError.js.map +1 -0
  11. package/dist/models/Exception/ConfigurationError.d.ts +4 -0
  12. package/dist/models/Exception/ConfigurationError.d.ts.map +1 -0
  13. package/dist/models/Exception/ConfigurationError.js +11 -0
  14. package/dist/models/Exception/ConfigurationError.js.map +1 -0
  15. package/dist/models/Exception/MeteringError.d.ts +5 -0
  16. package/dist/models/Exception/MeteringError.d.ts.map +1 -0
  17. package/dist/models/Exception/MeteringError.js +12 -0
  18. package/dist/models/Exception/MeteringError.js.map +1 -0
  19. package/dist/models/Exception/MiddlewareActivationError.d.ts +4 -0
  20. package/dist/models/Exception/MiddlewareActivationError.d.ts.map +1 -0
  21. package/dist/models/Exception/MiddlewareActivationError.js +11 -0
  22. package/dist/models/Exception/MiddlewareActivationError.js.map +1 -0
  23. package/dist/models/Exception/StreamTrackingError.d.ts +4 -0
  24. package/dist/models/Exception/StreamTrackingError.d.ts.map +1 -0
  25. package/dist/models/Exception/StreamTrackingError.js +11 -0
  26. package/dist/models/Exception/StreamTrackingError.js.map +1 -0
  27. package/dist/models/Exception/TokenCountingError.d.ts +4 -0
  28. package/dist/models/Exception/TokenCountingError.d.ts.map +1 -0
  29. package/dist/models/Exception/TokenCountingError.js +11 -0
  30. package/dist/models/Exception/TokenCountingError.js.map +1 -0
  31. package/dist/models/Exception/index.d.ts +7 -0
  32. package/dist/models/Exception/index.d.ts.map +1 -0
  33. package/dist/models/Exception/index.js +23 -0
  34. package/dist/models/Exception/index.js.map +1 -0
  35. package/dist/models/Logger.d.ts +10 -0
  36. package/dist/models/Logger.d.ts.map +1 -0
  37. package/dist/models/Logger.js +36 -0
  38. package/dist/models/Logger.js.map +1 -0
  39. package/dist/models/index.d.ts +3 -0
  40. package/dist/models/index.d.ts.map +1 -0
  41. package/dist/models/index.js +19 -0
  42. package/dist/models/index.js.map +1 -0
  43. package/dist/perplexity-ai/client.d.ts +31 -0
  44. package/dist/perplexity-ai/client.d.ts.map +1 -0
  45. package/dist/perplexity-ai/client.js +75 -0
  46. package/dist/perplexity-ai/client.js.map +1 -0
  47. package/dist/perplexity-ai/index.d.ts +3 -0
  48. package/dist/perplexity-ai/index.d.ts.map +1 -0
  49. package/dist/perplexity-ai/index.js +19 -0
  50. package/dist/perplexity-ai/index.js.map +1 -0
  51. package/dist/perplexity-ai/middleware.d.ts +2 -0
  52. package/dist/perplexity-ai/middleware.d.ts.map +1 -0
  53. package/dist/perplexity-ai/middleware.js +163 -0
  54. package/dist/perplexity-ai/middleware.js.map +1 -0
  55. package/dist/types/context.d.ts +9 -0
  56. package/dist/types/context.d.ts.map +1 -0
  57. package/dist/types/context.js +3 -0
  58. package/dist/types/context.js.map +1 -0
  59. package/dist/types/index.d.ts +12 -0
  60. package/dist/types/index.d.ts.map +1 -0
  61. package/dist/types/index.js +28 -0
  62. package/dist/types/index.js.map +1 -0
  63. package/dist/types/metering.d.ts +28 -0
  64. package/dist/types/metering.d.ts.map +1 -0
  65. package/dist/types/metering.js +3 -0
  66. package/dist/types/metering.js.map +1 -0
  67. package/dist/types/modelInfo.d.ts +6 -0
  68. package/dist/types/modelInfo.d.ts.map +1 -0
  69. package/dist/types/modelInfo.js +3 -0
  70. package/dist/types/modelInfo.js.map +1 -0
  71. package/dist/types/operation.d.ts +5 -0
  72. package/dist/types/operation.d.ts.map +1 -0
  73. package/dist/types/operation.js +9 -0
  74. package/dist/types/operation.js.map +1 -0
  75. package/dist/types/perplexityConfig.d.ts +5 -0
  76. package/dist/types/perplexityConfig.d.ts.map +1 -0
  77. package/dist/types/perplexityConfig.js +3 -0
  78. package/dist/types/perplexityConfig.js.map +1 -0
  79. package/dist/types/provider.d.ts +4 -0
  80. package/dist/types/provider.d.ts.map +1 -0
  81. package/dist/types/provider.js +8 -0
  82. package/dist/types/provider.js.map +1 -0
  83. package/dist/types/reveniumConfig.d.ts +6 -0
  84. package/dist/types/reveniumConfig.d.ts.map +1 -0
  85. package/dist/types/reveniumConfig.js +3 -0
  86. package/dist/types/reveniumConfig.js.map +1 -0
  87. package/dist/types/streamTracker.d.ts +9 -0
  88. package/dist/types/streamTracker.d.ts.map +1 -0
  89. package/dist/types/streamTracker.js +3 -0
  90. package/dist/types/streamTracker.js.map +1 -0
  91. package/dist/types/subscriber.d.ts +9 -0
  92. package/dist/types/subscriber.d.ts.map +1 -0
  93. package/dist/types/subscriber.js +3 -0
  94. package/dist/types/subscriber.js.map +1 -0
  95. package/dist/types/tokenCounts.d.ts +6 -0
  96. package/dist/types/tokenCounts.d.ts.map +1 -0
  97. package/dist/types/tokenCounts.js +3 -0
  98. package/dist/types/tokenCounts.js.map +1 -0
  99. package/dist/types/usageData.d.ts +16 -0
  100. package/dist/types/usageData.d.ts.map +1 -0
  101. package/dist/types/usageData.js +3 -0
  102. package/dist/types/usageData.js.map +1 -0
  103. package/dist/types/usageMetadata.d.ts +14 -0
  104. package/dist/types/usageMetadata.d.ts.map +1 -0
  105. package/dist/types/usageMetadata.js +3 -0
  106. package/dist/types/usageMetadata.js.map +1 -0
  107. package/dist/utils/activeMiddleware.d.ts +2 -0
  108. package/dist/utils/activeMiddleware.d.ts.map +1 -0
  109. package/dist/utils/activeMiddleware.js +24 -0
  110. package/dist/utils/activeMiddleware.js.map +1 -0
  111. package/dist/utils/askConsole.d.ts +2 -0
  112. package/dist/utils/askConsole.d.ts.map +1 -0
  113. package/dist/utils/askConsole.js +20 -0
  114. package/dist/utils/askConsole.js.map +1 -0
  115. package/dist/utils/calculateDurationMs.d.ts +2 -0
  116. package/dist/utils/calculateDurationMs.d.ts.map +1 -0
  117. package/dist/utils/calculateDurationMs.js +7 -0
  118. package/dist/utils/calculateDurationMs.js.map +1 -0
  119. package/dist/utils/constants/constants.d.ts +9 -0
  120. package/dist/utils/constants/constants.d.ts.map +1 -0
  121. package/dist/utils/constants/constants.js +19 -0
  122. package/dist/utils/constants/constants.js.map +1 -0
  123. package/dist/utils/constants/messages.d.ts +19 -0
  124. package/dist/utils/constants/messages.d.ts.map +1 -0
  125. package/dist/utils/constants/messages.js +22 -0
  126. package/dist/utils/constants/messages.js.map +1 -0
  127. package/dist/utils/createMeteringRequest.d.ts +3 -0
  128. package/dist/utils/createMeteringRequest.d.ts.map +1 -0
  129. package/dist/utils/createMeteringRequest.js +10 -0
  130. package/dist/utils/createMeteringRequest.js.map +1 -0
  131. package/dist/utils/createPerplexityMetadata.d.ts +3 -0
  132. package/dist/utils/createPerplexityMetadata.d.ts.map +1 -0
  133. package/dist/utils/createPerplexityMetadata.js +12 -0
  134. package/dist/utils/createPerplexityMetadata.js.map +1 -0
  135. package/dist/utils/extractModelName.d.ts +2 -0
  136. package/dist/utils/extractModelName.d.ts.map +1 -0
  137. package/dist/utils/extractModelName.js +15 -0
  138. package/dist/utils/extractModelName.js.map +1 -0
  139. package/dist/utils/extractPerplexityTokenCounts.d.ts +3 -0
  140. package/dist/utils/extractPerplexityTokenCounts.d.ts.map +1 -0
  141. package/dist/utils/extractPerplexityTokenCounts.js +17 -0
  142. package/dist/utils/extractPerplexityTokenCounts.js.map +1 -0
  143. package/dist/utils/extractStopReason.d.ts +2 -0
  144. package/dist/utils/extractStopReason.d.ts.map +1 -0
  145. package/dist/utils/extractStopReason.js +14 -0
  146. package/dist/utils/extractStopReason.js.map +1 -0
  147. package/dist/utils/extractUsageMetadata.d.ts +3 -0
  148. package/dist/utils/extractUsageMetadata.d.ts.map +1 -0
  149. package/dist/utils/extractUsageMetadata.js +15 -0
  150. package/dist/utils/extractUsageMetadata.js.map +1 -0
  151. package/dist/utils/formatTimestamp.d.ts +2 -0
  152. package/dist/utils/formatTimestamp.d.ts.map +1 -0
  153. package/dist/utils/formatTimestamp.js +7 -0
  154. package/dist/utils/formatTimestamp.js.map +1 -0
  155. package/dist/utils/generateTransactionId.d.ts +2 -0
  156. package/dist/utils/generateTransactionId.d.ts.map +1 -0
  157. package/dist/utils/generateTransactionId.js +8 -0
  158. package/dist/utils/generateTransactionId.js.map +1 -0
  159. package/dist/utils/getEnv.d.ts +4 -0
  160. package/dist/utils/getEnv.d.ts.map +1 -0
  161. package/dist/utils/getEnv.js +16 -0
  162. package/dist/utils/getEnv.js.map +1 -0
  163. package/dist/utils/index.d.ts +13 -0
  164. package/dist/utils/index.d.ts.map +1 -0
  165. package/dist/utils/index.js +29 -0
  166. package/dist/utils/index.js.map +1 -0
  167. package/dist/utils/safeExtract.d.ts +2 -0
  168. package/dist/utils/safeExtract.d.ts.map +1 -0
  169. package/dist/utils/safeExtract.js +22 -0
  170. package/dist/utils/safeExtract.js.map +1 -0
  171. package/dist/utils/sendMeteringData.d.ts +3 -0
  172. package/dist/utils/sendMeteringData.d.ts.map +1 -0
  173. package/dist/utils/sendMeteringData.js +35 -0
  174. package/dist/utils/sendMeteringData.js.map +1 -0
  175. package/dist/utils/verifyLogVerbose.d.ts +2 -0
  176. package/dist/utils/verifyLogVerbose.d.ts.map +1 -0
  177. package/dist/utils/verifyLogVerbose.js +10 -0
  178. package/dist/utils/verifyLogVerbose.js.map +1 -0
  179. package/examples/README.md +316 -0
  180. package/examples/basic-client-example.ts +36 -0
  181. package/examples/metadata-example.ts +92 -0
  182. package/examples/multiple-models-example.ts +65 -0
  183. package/examples/openai-client-example.ts +76 -0
  184. package/examples/perplexity_basic_example.ts +24 -0
  185. package/examples/perplexity_middleware_example.ts +222 -0
  186. package/examples/run-all-examples.ts +118 -0
  187. package/examples/simple-test.ts +278 -0
  188. package/examples/streaming-example.ts +46 -0
  189. package/package.json +54 -0
  190. package/src/index.ts +38 -0
  191. package/src/models/Exception/ApiResponseError.ts +6 -0
  192. package/src/models/Exception/ConfigurationError.ts +6 -0
  193. package/src/models/Exception/MeteringError.ts +6 -0
  194. package/src/models/Exception/MiddlewareActivationError.ts +6 -0
  195. package/src/models/Exception/StreamTrackingError.ts +6 -0
  196. package/src/models/Exception/TokenCountingError.ts +6 -0
  197. package/src/models/Exception/index.ts +6 -0
  198. package/src/models/Logger.ts +38 -0
  199. package/src/models/index.ts +2 -0
  200. package/src/perplexity-ai/client.ts +97 -0
  201. package/src/perplexity-ai/index.ts +2 -0
  202. package/src/perplexity-ai/middleware.ts +259 -0
  203. package/src/types/context.ts +9 -0
  204. package/src/types/index.ts +11 -0
  205. package/src/types/metering.ts +28 -0
  206. package/src/types/modelInfo.ts +5 -0
  207. package/src/types/operation.ts +4 -0
  208. package/src/types/perplexityConfig.ts +4 -0
  209. package/src/types/provider.ts +3 -0
  210. package/src/types/reveniumConfig.ts +5 -0
  211. package/src/types/streamTracker.ts +9 -0
  212. package/src/types/subscriber.ts +8 -0
  213. package/src/types/tokenCounts.ts +5 -0
  214. package/src/types/usageData.ts +16 -0
  215. package/src/types/usageMetadata.ts +13 -0
  216. package/src/utils/activeMiddleware.ts +34 -0
  217. package/src/utils/askConsole.ts +15 -0
  218. package/src/utils/calculateDurationMs.ts +3 -0
  219. package/src/utils/constants/constants.ts +17 -0
  220. package/src/utils/constants/messages.ts +35 -0
  221. package/src/utils/createMeteringRequest.ts +16 -0
  222. package/src/utils/createPerplexityMetadata.ts +11 -0
  223. package/src/utils/extractModelName.ts +14 -0
  224. package/src/utils/extractPerplexityTokenCounts.ts +16 -0
  225. package/src/utils/extractStopReason.ts +13 -0
  226. package/src/utils/extractUsageMetadata.ts +15 -0
  227. package/src/utils/formatTimestamp.ts +3 -0
  228. package/src/utils/generateTransactionId.ts +5 -0
  229. package/src/utils/getEnv.ts +16 -0
  230. package/src/utils/index.ts +12 -0
  231. package/src/utils/safeExtract.ts +18 -0
  232. package/src/utils/sendMeteringData.ts +46 -0
  233. package/src/utils/verifyLogVerbose.ts +7 -0
  234. package/tsconfig.json +23 -0
@@ -0,0 +1,259 @@
1
+ import { logger, Logger } from "../models";
2
+ import {
3
+ OperationType,
4
+ Provider,
5
+ UsageMetadata,
6
+ StreamTracker,
7
+ } from "../types";
8
+ import {
9
+ formatTimestamp,
10
+ calculateDurationMs,
11
+ extractPerplexityTokenCounts,
12
+ extractStopReason,
13
+ extractModelName,
14
+ extractUsageMetadata,
15
+ createMeteringRequest,
16
+ sendMeteringData,
17
+ generateTransactionId,
18
+ } from "../utils";
19
+ import { ConfigurationError, StreamTrackingError } from "../models";
20
+ import {
21
+ DEBUG_MIDDLEWARE_ALREADY_INITIALIZED_MESSAGE,
22
+ DEBUG_MIDDLEWARE_FAILED_TO_INITIALIZE_MESSAGE,
23
+ DEBUG_MIDDLEWARE_INITIALIZED_MESSAGE,
24
+ FAILED_TO_INITIALIZE_PERPLEXITY_MIDDLEWARE_MESSAGE,
25
+ DEBUG_MIDDLEWARE_INTERCEPTING_PERPLEXITY_REQUESTS_MESSAGE,
26
+ WARNING_REVENIUM_METERING_API_KEY_NOT_FOUND_MESSAGE,
27
+ FAILED_TO_PARSE_REQUEST_BODY_FOR_METADATA_EXTRACTION_MESSAGE,
28
+ DEBUG_MIDDLEWARE_PERPLEXITY_API_REQUEST_INTERCEPTED_MESSAGE,
29
+ ERROR_PERPLEXITY_API_REQUEST_FAILED_MESSAGE,
30
+ WARNING_FAILED_TO_SEND_METERING_DATA_MESSAGE,
31
+ DEBUG_MIDDLEWARE_PERPLEXITY_API_RESPONSE_METERING_COMPLETED_MESSAGE,
32
+ DEBUG_SKIPPING_METERING_DATA_SEND_MESSAGE,
33
+ ERROR_FAILED_TO_PROCESS_PERPLEXITY_RESPONSE_MESSAGE,
34
+ } from "../utils/constants/messages";
35
+ import {
36
+ CURRENT_PERPLEXITY_BASE_URL,
37
+ DEFAULT_PERPLEXITY_MODEL,
38
+ REVENIUM_METERING_BASE_URL,
39
+ } from "../utils/constants/constants";
40
+ import { Subscriber } from "../types/subscriber";
41
+ import {
42
+ getPerplexityBaseUrl,
43
+ getReveniumApiKey,
44
+ getReveniumBaseUrl,
45
+ } from "../utils/getEnv";
46
+
47
+ // Stream tracking storage
48
+ const streamTrackers: Map<string, StreamTracker> = new Map<
49
+ string,
50
+ StreamTracker
51
+ >();
52
+
53
+ // Track if middleware has been initialized to avoid double initialization
54
+ let middlewareInitialized: boolean = false;
55
+
56
+ //Initialize Perplexity AI middleware.
57
+ function initializePerplexityMiddleware(): void {
58
+ if (middlewareInitialized) {
59
+ console.log(DEBUG_MIDDLEWARE_ALREADY_INITIALIZED_MESSAGE);
60
+ return;
61
+ }
62
+
63
+ // Always initialize middleware, even without API key
64
+ if (!getReveniumApiKey()) {
65
+ Logger.warning(WARNING_REVENIUM_METERING_API_KEY_NOT_FOUND_MESSAGE);
66
+ }
67
+
68
+ try {
69
+ // Intercept HTTP requests to Perplexity API
70
+ interceptPerplexityHTTPRequests();
71
+
72
+ // Mark as initialized
73
+ middlewareInitialized = true;
74
+ console.log(DEBUG_MIDDLEWARE_INITIALIZED_MESSAGE);
75
+ } catch (error) {
76
+ console.error(DEBUG_MIDDLEWARE_FAILED_TO_INITIALIZE_MESSAGE, error);
77
+ throw new ConfigurationError(
78
+ `${FAILED_TO_INITIALIZE_PERPLEXITY_MIDDLEWARE_MESSAGE}: ${error}`
79
+ );
80
+ }
81
+ }
82
+
83
+ //Intercept HTTP requests to Perplexity API
84
+ function interceptPerplexityHTTPRequests(): void {
85
+ // Store original fetch function
86
+ const originalFetch = global.fetch;
87
+
88
+ // Override fetch to intercept Perplexity API calls
89
+ global.fetch = async function (
90
+ input: string | URL | Request,
91
+ init?: RequestInit
92
+ ): Promise<Response> {
93
+ const url = typeof input === "string" ? input : input.toString();
94
+ console.log("🔍 [DEBUG] URL:", url);
95
+
96
+ // Check if this is a Perplexity API request
97
+ if (
98
+ url.includes(getPerplexityBaseUrl()) ||
99
+ url.includes("api.perplexity.ai")
100
+ ) {
101
+ return await handlePerplexityRequest(url, init, originalFetch);
102
+ }
103
+
104
+ // For non-Perplexity requests, use original fetch
105
+ return originalFetch(input, init);
106
+ };
107
+
108
+ console.log(DEBUG_MIDDLEWARE_INTERCEPTING_PERPLEXITY_REQUESTS_MESSAGE);
109
+ }
110
+
111
+ //Handle Perplexity API requests
112
+ async function handlePerplexityRequest(
113
+ url: string,
114
+ init: RequestInit | undefined,
115
+ originalFetch: typeof fetch
116
+ ): Promise<Response> {
117
+ const startTime = new Date();
118
+ const transactionId = generateTransactionId();
119
+
120
+ // Extract request body for metadata
121
+ let requestBody: any = {};
122
+ if (init?.body) {
123
+ try {
124
+ requestBody =
125
+ typeof init.body === "string" ? JSON.parse(init.body) : init.body;
126
+ } catch (error) {
127
+ Logger.warning(
128
+ FAILED_TO_PARSE_REQUEST_BODY_FOR_METADATA_EXTRACTION_MESSAGE
129
+ );
130
+ }
131
+ }
132
+
133
+ // Extract usage metadata
134
+ const usageMetadata = extractUsageMetadata(requestBody, null);
135
+ const model = requestBody.model || DEFAULT_PERPLEXITY_MODEL;
136
+
137
+ Logger.debug(DEBUG_MIDDLEWARE_PERPLEXITY_API_REQUEST_INTERCEPTED_MESSAGE, {
138
+ transactionId,
139
+ model,
140
+ url,
141
+ });
142
+
143
+ try {
144
+ // Make the original request
145
+ const response = await originalFetch(url, init);
146
+
147
+ // Clone the response so we can read it and still return it
148
+ const responseClone = response.clone();
149
+
150
+ // Process the response asynchronously (don't wait for it)
151
+ processPerplexityResponse(
152
+ responseClone,
153
+ transactionId,
154
+ model,
155
+ startTime,
156
+ usageMetadata
157
+ );
158
+
159
+ // Return the original response immediately
160
+ return response;
161
+ } catch (error) {
162
+ Logger.error(ERROR_PERPLEXITY_API_REQUEST_FAILED_MESSAGE, {
163
+ transactionId,
164
+ error,
165
+ });
166
+ throw error;
167
+ }
168
+ }
169
+
170
+ //Process Perplexity API response
171
+ async function processPerplexityResponse(
172
+ response: Response,
173
+ transactionId: string,
174
+ model: string,
175
+ startTime: Date,
176
+ usageMetadata?: UsageMetadata,
177
+ subscriber?: Subscriber,
178
+ taskType?: string,
179
+ costType?: string,
180
+ operationType?: string,
181
+ organizationId?: string,
182
+ productId?: string,
183
+ isStreamed?: boolean
184
+ ): Promise<void> {
185
+ try {
186
+ const endTime = new Date();
187
+ const duration = calculateDurationMs(startTime, endTime);
188
+
189
+ // Parse response body
190
+ const responseData = await response.json();
191
+
192
+ // Extract token counts and other data
193
+ const tokenCounts = extractPerplexityTokenCounts(responseData);
194
+ const stopReason = extractStopReason(responseData);
195
+ const modelName = extractModelName(responseData, model);
196
+
197
+ // Send metering data (only if API key is available)
198
+ if (getReveniumApiKey()) {
199
+ try {
200
+ await sendMeteringData(
201
+ {
202
+ stopReason: "END",
203
+ costType: costType || "AI",
204
+ isStreamed: isStreamed || true,
205
+ taskType: taskType || "chat",
206
+ agent: "perplexity-ai",
207
+ operationType: operationType || "CHAT",
208
+ inputTokenCount: tokenCounts.inputTokens,
209
+ outputTokenCount: tokenCounts.outputTokens,
210
+ reasoningTokenCount: 0,
211
+ cacheCreationTokenCount: 0,
212
+ cacheReadTokenCount: 0,
213
+ totalTokenCount: tokenCounts.totalTokens,
214
+ organizationId: organizationId || "my-customers-name",
215
+ productId: productId || "free-trial",
216
+ subscriber: subscriber || {
217
+ id: `user-${generateTransactionId()}`,
218
+ email: `user@perplexity.ai`,
219
+ credential: {
220
+ name: "keyname",
221
+ value: "keyValue",
222
+ },
223
+ },
224
+ model: modelName,
225
+ transactionId,
226
+ responseTime: formatTimestamp(endTime),
227
+ requestDuration: duration,
228
+ provider: Provider.PERPLEXITY,
229
+ requestTime: formatTimestamp(startTime),
230
+ completionStartTime: formatTimestamp(endTime),
231
+ timeToFirstToken: 0,
232
+ middleware_source: "node",
233
+ },
234
+ getReveniumApiKey(),
235
+ getReveniumBaseUrl()
236
+ );
237
+ } catch (error) {
238
+ Logger.warning(WARNING_FAILED_TO_SEND_METERING_DATA_MESSAGE, { error });
239
+ }
240
+ } else {
241
+ Logger.debug(DEBUG_SKIPPING_METERING_DATA_SEND_MESSAGE);
242
+ }
243
+
244
+ Logger.debug(
245
+ DEBUG_MIDDLEWARE_PERPLEXITY_API_RESPONSE_METERING_COMPLETED_MESSAGE,
246
+ {
247
+ transactionId,
248
+ }
249
+ );
250
+ } catch (error) {
251
+ Logger.error(ERROR_FAILED_TO_PROCESS_PERPLEXITY_RESPONSE_MESSAGE, {
252
+ transactionId,
253
+ error,
254
+ });
255
+ }
256
+ }
257
+
258
+ // Initialize middleware when this module is imported
259
+ initializePerplexityMiddleware();
@@ -0,0 +1,9 @@
1
+ import { UsageMetadata } from "./usageMetadata";
2
+
3
+ export interface Context {
4
+ transactionId: string;
5
+ startTime: Date;
6
+ model: string;
7
+ operationType: string;
8
+ usageMetadata?: UsageMetadata;
9
+ }
@@ -0,0 +1,11 @@
1
+ export * from "./context";
2
+ export * from "./perplexityConfig";
3
+ export * from "./metering";
4
+ export * from "./modelInfo";
5
+ export * from "./operation";
6
+ export * from "./provider";
7
+ export * from "./reveniumConfig";
8
+ export * from "./streamTracker";
9
+ export * from "./tokenCounts";
10
+ export * from "./usageData";
11
+ export * from "./usageMetadata";
@@ -0,0 +1,28 @@
1
+ import { Subscriber } from "./subscriber";
2
+
3
+ export interface MeteringRequest {
4
+ stopReason: string;
5
+ costType: string;
6
+ isStreamed: boolean;
7
+ taskType: string;
8
+ agent: string;
9
+ operationType: string;
10
+ inputTokenCount: number;
11
+ outputTokenCount: number;
12
+ reasoningTokenCount: number;
13
+ cacheCreationTokenCount: number;
14
+ cacheReadTokenCount: number;
15
+ totalTokenCount: number;
16
+ organizationId: string;
17
+ productId: string;
18
+ subscriber: Subscriber;
19
+ model: string;
20
+ transactionId: string;
21
+ responseTime: string;
22
+ requestDuration: number;
23
+ provider: string;
24
+ requestTime: string;
25
+ completionStartTime: string;
26
+ timeToFirstToken: number;
27
+ middleware_source: string;
28
+ }
@@ -0,0 +1,5 @@
1
+ export interface ModelInfo {
2
+ name: string;
3
+ provider: string;
4
+ contextLength?: number;
5
+ }
@@ -0,0 +1,4 @@
1
+ export enum OperationType {
2
+ CHAT = "chat",
3
+ STREAMING = "streaming"
4
+ }
@@ -0,0 +1,4 @@
1
+ export interface PerplexityConfig {
2
+ apiKey: string;
3
+ baseUrl: string;
4
+ }
@@ -0,0 +1,3 @@
1
+ export enum Provider {
2
+ PERPLEXITY = "Perplexity"
3
+ }
@@ -0,0 +1,5 @@
1
+ export interface ReveniumConfig {
2
+ apiKey: string;
3
+ baseUrl: string;
4
+ logLevel: string;
5
+ }
@@ -0,0 +1,9 @@
1
+ import { UsageMetadata } from "./usageMetadata";
2
+
3
+ export interface StreamTracker {
4
+ transactionId: string;
5
+ startTime: Date;
6
+ firstTokenTime?: Date;
7
+ isComplete: boolean;
8
+ usageMetadata?: UsageMetadata;
9
+ }
@@ -0,0 +1,8 @@
1
+ export interface Subscriber {
2
+ id: string;
3
+ email: string;
4
+ credential: {
5
+ name: string;
6
+ value: string;
7
+ };
8
+ }
@@ -0,0 +1,5 @@
1
+ export interface TokenCounts {
2
+ inputTokens: number;
3
+ outputTokens: number;
4
+ totalTokens: number;
5
+ }
@@ -0,0 +1,16 @@
1
+ import { TokenCounts } from "./tokenCounts";
2
+ import { UsageMetadata } from "./usageMetadata";
3
+
4
+ export interface UsageData {
5
+ transactionId: string;
6
+ model: string;
7
+ tokenCounts: TokenCounts;
8
+ stopReason: string;
9
+ startTime: string;
10
+ endTime: string;
11
+ duration: number;
12
+ operationType: string;
13
+ isStreaming: boolean;
14
+ streamId: string;
15
+ usageMetadata?: UsageMetadata;
16
+ }
@@ -0,0 +1,13 @@
1
+ export interface UsageMetadata {
2
+ traceId?: string;
3
+ taskType?: string;
4
+ subscriberEmail?: string;
5
+ subscriberId?: string;
6
+ subscriberCredentialName?: string;
7
+ subscriberCredential?: string;
8
+ organizationId?: string;
9
+ subscriptionId?: string;
10
+ productId?: string;
11
+ agent?: string;
12
+ responseQualityScore?: number;
13
+ }
@@ -0,0 +1,34 @@
1
+ import { Logger } from "../models";
2
+ import { MiddlewareActivationError } from "../models";
3
+ import { verifyLogVerbose } from "./verifyLogVerbose";
4
+
5
+ export function activeMiddleware(
6
+ verboseStartup: boolean,
7
+ activeSDKs: string[],
8
+ sdkType: string
9
+ ): void {
10
+ try {
11
+ verifyLogVerbose(
12
+ verboseStartup,
13
+ `Attempting to activate ${sdkType} middleware...`
14
+ );
15
+
16
+ // For Perplexity, we'll intercept HTTP requests to the Perplexity API
17
+ // This is a simpler approach since Perplexity doesn't have an official SDK
18
+ interceptPerplexityRequests();
19
+
20
+ activeSDKs.push(sdkType);
21
+ verifyLogVerbose(
22
+ verboseStartup,
23
+ `${sdkType} middleware activated successfully`
24
+ );
25
+ } catch (error) {
26
+ Logger.warning(`Failed to activate ${sdkType} middleware: ${error}`);
27
+ }
28
+ }
29
+
30
+ function interceptPerplexityRequests(): void {
31
+ // This will be implemented in the main middleware file
32
+ // For now, we just log that we're ready to intercept
33
+ Logger.debug("Perplexity request interceptor ready");
34
+ }
@@ -0,0 +1,15 @@
1
+ import readline from "readline";
2
+
3
+ export function askConsole(question: string): Promise<string> {
4
+ const rl = readline.createInterface({
5
+ input: process.stdin,
6
+ output: process.stdout,
7
+ });
8
+
9
+ return new Promise((resolve) => {
10
+ rl.question(question, (content) => {
11
+ rl.close();
12
+ resolve(content);
13
+ });
14
+ });
15
+ }
@@ -0,0 +1,3 @@
1
+ export function calculateDurationMs(startTime: Date, endTime: Date): number {
2
+ return endTime.getTime() - startTime.getTime();
3
+ }
@@ -0,0 +1,17 @@
1
+ export const TIMEOUT_MS = 10000; // 10 seconds
2
+ export const STATUS_CODE: Record<string, number> = {
3
+ SUCCESS: 200,
4
+ CREATED: 201,
5
+ };
6
+ export const SAFE_EXTRACT: Record<string, string> = {
7
+ INPUT_TOKENS: "usage.prompt_tokens",
8
+ OUTPUT_TOKENS: "usage.completion_tokens",
9
+ TOTAL_TOKENS: "usage.total_tokens",
10
+ };
11
+
12
+ export const MODEL: string = "model";
13
+ export const REVENIUM_METERING_BASE_URL: string =
14
+ "https://api.revenium.io/meter/v2";
15
+ export const CURRENT_PERPLEXITY_BASE_URL: string = "https://api.perplexity.ai";
16
+ export const DEFAULT_PERPLEXITY_MODEL: string = "perplexity-online";
17
+ export const LOG_LEVELS: string[] = ["DEBUG", "INFO", "WARNING", "ERROR"];
@@ -0,0 +1,35 @@
1
+ export const PERPLEXITY_REQUIRED_API_KEY_MESSAGE =
2
+ "PERPLEXITY_API_KEY is required. Set it in environment variables or pass it to the constructor.";
3
+ export const PERPLEXITY_CLIENT_INITIALIZED_MESSAGE =
4
+ "Perplexity AI client initialized with middleware";
5
+ export const PERPLEXITY_CHAT_COMPLETION_FAILED_MESSAGE =
6
+ "Chat completion failed";
7
+ export const PERPLEXITY_STREAMING_CHAT_COMPLETION_FAILED_MESSAGE =
8
+ "Streaming chat completion failed";
9
+ export const PERPLEXITY_GET_MODELS_FAILED_MESSAGE = "Failed to get models";
10
+ export const DEBUG_MIDDLEWARE_ALREADY_INITIALIZED_MESSAGE =
11
+ "[DEBUG] Middleware already initialized, skipping...";
12
+ export const WARNING_REVENIUM_METERING_API_KEY_NOT_FOUND_MESSAGE =
13
+ "REVENIUM_METERING_API_KEY not found - metering will be disabled but middleware will still intercept calls";
14
+ export const DEBUG_MIDDLEWARE_INITIALIZED_MESSAGE =
15
+ "🔍 [DEBUG] Perplexity middleware initialized successfully";
16
+ export const DEBUG_MIDDLEWARE_FAILED_TO_INITIALIZE_MESSAGE =
17
+ "🔍 [DEBUG] Failed to initialize Perplexity middleware";
18
+ export const FAILED_TO_INITIALIZE_PERPLEXITY_MIDDLEWARE_MESSAGE =
19
+ "Failed to initialize Perplexity middleware";
20
+ export const DEBUG_MIDDLEWARE_INTERCEPTING_PERPLEXITY_REQUESTS_MESSAGE =
21
+ "🔍 [DEBUG] Perplexity HTTP interceptor installed";
22
+ export const FAILED_TO_PARSE_REQUEST_BODY_FOR_METADATA_EXTRACTION_MESSAGE =
23
+ "Failed to parse request body for metadata extraction";
24
+ export const DEBUG_MIDDLEWARE_PERPLEXITY_API_REQUEST_INTERCEPTED_MESSAGE =
25
+ "🔍 [DEBUG] Perplexity API request intercepted";
26
+ export const ERROR_PERPLEXITY_API_REQUEST_FAILED_MESSAGE =
27
+ "Perplexity API request failed";
28
+ export const WARNING_FAILED_TO_SEND_METERING_DATA_MESSAGE =
29
+ "Failed to send metering data, but continuing with response";
30
+ export const DEBUG_MIDDLEWARE_PERPLEXITY_API_RESPONSE_METERING_COMPLETED_MESSAGE =
31
+ "🔍 [DEBUG] Perplexity API response metering completed";
32
+ export const DEBUG_SKIPPING_METERING_DATA_SEND_MESSAGE =
33
+ "Skipping metering data send - no API key available";
34
+ export const ERROR_FAILED_TO_PROCESS_PERPLEXITY_RESPONSE_MESSAGE =
35
+ "Failed to process Perplexity response";
@@ -0,0 +1,16 @@
1
+ import {
2
+ MeteringRequest,
3
+ OperationType,
4
+ Provider,
5
+ UsageMetadata,
6
+ } from "../types";
7
+ import { formatTimestamp } from "./formatTimestamp";
8
+
9
+ export function createMeteringRequest(
10
+ request: MeteringRequest
11
+ ): MeteringRequest {
12
+ return {
13
+ ...request,
14
+ middleware_source: "node",
15
+ };
16
+ }
@@ -0,0 +1,11 @@
1
+ import { UsageMetadata } from "../types";
2
+
3
+ export function createPerplexityMetadata(metadata?: UsageMetadata): any {
4
+ if (!metadata) {
5
+ return {};
6
+ }
7
+
8
+ return {
9
+ usageMetadata: metadata,
10
+ };
11
+ }
@@ -0,0 +1,14 @@
1
+ import { MODEL } from "./constants/constants";
2
+ import { safeExtract } from "./safeExtract";
3
+
4
+ export function extractModelName(response: any, defaultModel: string): string {
5
+ // Try to extract model name from response
6
+ const modelFromResponse = safeExtract(response, MODEL, "");
7
+
8
+ if (modelFromResponse) {
9
+ return modelFromResponse;
10
+ }
11
+
12
+ // Fallback to default model
13
+ return defaultModel;
14
+ }
@@ -0,0 +1,16 @@
1
+ import { TokenCounts } from "../types";
2
+ import { SAFE_EXTRACT } from "./constants/constants";
3
+ import { safeExtract } from "./safeExtract";
4
+
5
+ export function extractPerplexityTokenCounts(response: any): TokenCounts {
6
+ // Extract token counts from Perplexity response
7
+ const inputTokens = safeExtract(response, SAFE_EXTRACT.INPUT_TOKENS, 0);
8
+ const outputTokens = safeExtract(response, SAFE_EXTRACT.OUTPUT_TOKENS, 0);
9
+ const totalTokens = safeExtract(response, SAFE_EXTRACT.TOTAL_TOKENS, 0);
10
+
11
+ return {
12
+ inputTokens,
13
+ outputTokens,
14
+ totalTokens: totalTokens || inputTokens + outputTokens,
15
+ };
16
+ }
@@ -0,0 +1,13 @@
1
+ import { safeExtract } from "./safeExtract";
2
+
3
+ export function extractStopReason(response: any): string {
4
+ // Try to extract stop reason from Perplexity response
5
+ const stopReason = safeExtract(response, "choices.0.finish_reason", "");
6
+
7
+ if (stopReason) {
8
+ return stopReason;
9
+ }
10
+
11
+ // Fallback to a default reason
12
+ return "completed";
13
+ }
@@ -0,0 +1,15 @@
1
+ import { UsageMetadata } from '../types';
2
+
3
+ export function extractUsageMetadata(request: any, context: any): UsageMetadata | undefined {
4
+ // Check if usageMetadata is directly in the request
5
+ if (request && request.usageMetadata) {
6
+ return request.usageMetadata;
7
+ }
8
+
9
+ // Check if usageMetadata is in the context (for model-level metadata)
10
+ if (context && context._reveniumUsageMetadata) {
11
+ return context._reveniumUsageMetadata;
12
+ }
13
+
14
+ return undefined;
15
+ }
@@ -0,0 +1,3 @@
1
+ export function formatTimestamp(date: Date): string {
2
+ return date.toISOString();
3
+ }
@@ -0,0 +1,5 @@
1
+ import { v4 as uuidv4 } from 'uuid';
2
+
3
+ export function generateTransactionId(): string {
4
+ return uuidv4();
5
+ }
@@ -0,0 +1,16 @@
1
+ import {
2
+ CURRENT_PERPLEXITY_BASE_URL,
3
+ REVENIUM_METERING_BASE_URL,
4
+ } from "./constants/constants";
5
+
6
+ export function getReveniumApiKey(): string {
7
+ return process.env.REVENIUM_METERING_API_KEY ?? "";
8
+ }
9
+
10
+ export function getReveniumBaseUrl(): string {
11
+ return process.env.REVENIUM_METERING_BASE_URL || REVENIUM_METERING_BASE_URL;
12
+ }
13
+
14
+ export function getPerplexityBaseUrl(): string {
15
+ return process.env.PERPLEXITY_BASE_URL || CURRENT_PERPLEXITY_BASE_URL;
16
+ }
@@ -0,0 +1,12 @@
1
+ export * from "./activeMiddleware";
2
+ export * from "./calculateDurationMs";
3
+ export * from "./createMeteringRequest";
4
+ export * from "./createPerplexityMetadata";
5
+ export * from "./extractModelName";
6
+ export * from "./extractPerplexityTokenCounts";
7
+ export * from "./extractStopReason";
8
+ export * from "./extractUsageMetadata";
9
+ export * from "./formatTimestamp";
10
+ export * from "./generateTransactionId";
11
+ export * from "./safeExtract";
12
+ export * from "./sendMeteringData";
@@ -0,0 +1,18 @@
1
+ export function safeExtract<T>(obj: any, path: string, defaultValue: T): T {
2
+ try {
3
+ const keys = path.split(".");
4
+ let result = obj;
5
+
6
+ for (const key of keys) {
7
+ if (result && typeof result === "object" && key in result) {
8
+ result = result[key];
9
+ } else {
10
+ return defaultValue;
11
+ }
12
+ }
13
+
14
+ return result ?? defaultValue;
15
+ } catch (error) {
16
+ return defaultValue;
17
+ }
18
+ }