@revenium/perplexity 1.0.22 → 1.0.25

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 (87) hide show
  1. package/README.md +391 -154
  2. package/dist/interfaces/meteringResponse.d.ts +28 -27
  3. package/dist/interfaces/meteringResponse.js +2 -2
  4. package/dist/models/Metering.js +23 -20
  5. package/dist/v1/perplexityV1.service.js +166 -0
  6. package/dist/v2/perplexityV2.service.js +178 -0
  7. package/examples/v1/basic.ts +50 -0
  8. package/examples/v1/chat.ts +40 -0
  9. package/examples/v1/metadata.ts +49 -0
  10. package/examples/v1/streaming.ts +44 -0
  11. package/examples/v2/basic.ts +49 -0
  12. package/examples/v2/chat.ts +60 -0
  13. package/examples/v2/metadata.ts +71 -0
  14. package/examples/v2/streaming.ts +61 -0
  15. package/package.json +26 -11
  16. package/playground/v1/basic.js +50 -0
  17. package/playground/v1/chat.js +46 -0
  18. package/playground/v1/metadata.js +50 -0
  19. package/playground/v1/streaming.js +44 -0
  20. package/playground/v2/basic.js +49 -0
  21. package/playground/v2/chat.js +72 -0
  22. package/playground/v2/metadata.js +76 -0
  23. package/playground/v2/streaming.js +67 -0
  24. package/src/index.ts +14 -1
  25. package/src/interfaces/chatCompletionRequest.ts +7 -2
  26. package/src/interfaces/meteringResponse.ts +1 -0
  27. package/src/interfaces/perplexityResponse.ts +63 -0
  28. package/src/interfaces/perplexityStreaming.ts +56 -0
  29. package/src/models/Metering.ts +9 -2
  30. package/src/utils/constants/perplexityModels.ts +71 -0
  31. package/src/v1/perplexityV1.controller.ts +164 -0
  32. package/src/v1/perplexityV1.service.ts +230 -0
  33. package/src/v2/perplexityV2.controller.ts +219 -0
  34. package/src/v2/perplexityV2.service.ts +260 -0
  35. package/dist/index.js +0 -19
  36. package/dist/interfaces/chatCompletionRequest.d.ts +0 -9
  37. package/dist/interfaces/chatCompletionRequest.js +0 -2
  38. package/dist/interfaces/credential.d.ts +0 -4
  39. package/dist/interfaces/credential.js +0 -2
  40. package/dist/interfaces/meteringRequest.d.ts +0 -13
  41. package/dist/interfaces/meteringRequest.js +0 -2
  42. package/dist/interfaces/operation.d.ts +0 -4
  43. package/dist/interfaces/operation.js +0 -8
  44. package/dist/interfaces/subscriber.d.ts +0 -8
  45. package/dist/interfaces/subscriber.js +0 -2
  46. package/dist/interfaces/tokenCounts.d.ts +0 -7
  47. package/dist/interfaces/tokenCounts.js +0 -2
  48. package/dist/interfaces/usageMetadata.d.ts +0 -27
  49. package/dist/interfaces/usageMetadata.js +0 -2
  50. package/dist/middleware.d.ts +0 -22
  51. package/dist/middleware.js +0 -129
  52. package/dist/models/Logger.js +0 -35
  53. package/dist/models/Metering.d.ts +0 -9
  54. package/dist/utils/calculateDurationMs.d.ts +0 -1
  55. package/dist/utils/calculateDurationMs.js +0 -6
  56. package/dist/utils/constants/constants.d.ts +0 -6
  57. package/dist/utils/constants/constants.js +0 -11
  58. package/dist/utils/constants/logLevels.d.ts +0 -1
  59. package/dist/utils/constants/logLevels.js +0 -4
  60. package/dist/utils/constants/messages.d.ts +0 -5
  61. package/dist/utils/constants/messages.js +0 -8
  62. package/dist/utils/constants/models.d.ts +0 -1
  63. package/dist/utils/constants/models.js +0 -21
  64. package/dist/utils/extractTokenCount.d.ts +0 -2
  65. package/dist/utils/extractTokenCount.js +0 -28
  66. package/dist/utils/formatTimeStamp.d.ts +0 -1
  67. package/dist/utils/formatTimeStamp.js +0 -6
  68. package/dist/utils/generateTransactionId.d.ts +0 -1
  69. package/dist/utils/generateTransactionId.js +0 -7
  70. package/dist/utils/index.d.ts +0 -6
  71. package/dist/utils/index.js +0 -23
  72. package/dist/utils/loadEnv.d.ts +0 -1
  73. package/dist/utils/loadEnv.js +0 -7
  74. package/dist/utils/safeExtract.d.ts +0 -29
  75. package/dist/utils/safeExtract.js +0 -67
  76. package/examples/basic.ts +0 -17
  77. package/examples/chat-completions.ts +0 -22
  78. package/examples/enhanced.ts +0 -20
  79. package/examples/metadata.ts +0 -43
  80. package/examples/streaming.ts +0 -24
  81. package/playground/basic.js +0 -17
  82. package/playground/chat-completions.js +0 -22
  83. package/playground/enhanced.js +0 -20
  84. package/playground/metadata.js +0 -43
  85. package/playground/streaming.js +0 -24
  86. package/src/middleware.ts +0 -157
  87. /package/src/utils/{formatTimeStamp.ts → formatTimestamp.ts} +0 -0
@@ -0,0 +1,230 @@
1
+ import OpenAI from "openai";
2
+ import { logger } from "../models/Logger";
3
+ import { Metering } from "../models/Metering";
4
+ import { generateTransactionId } from "../utils/generateTransactionId";
5
+ import { IOperationType } from "../interfaces/operation";
6
+ import { IUsageMetadata } from "../interfaces/usageMetadata";
7
+ import { ITokenCounts } from "../interfaces/tokenCounts";
8
+ import {
9
+ IChatCompletionRequest,
10
+ IPerplexityMessage,
11
+ } from "../interfaces/chatCompletionRequest";
12
+ import {
13
+ IPerplexityResponseChat,
14
+ IPerplexityRawResponse,
15
+ } from "../interfaces/perplexityResponse";
16
+ import {
17
+ IPerplexityStreamingResponse,
18
+ IPerplexityStreamChunk,
19
+ } from "../interfaces/perplexityStreaming";
20
+
21
+ import {
22
+ PERPLEXITY_API_BASE_URL,
23
+ PERPLEXITY_API_KEY,
24
+ PERPLEXITY_CLIENT_INITIALIZED_MESSAGE,
25
+ REVENIUM_METERING_API_KEY,
26
+ REVENIUM_METERING_BASE_URL,
27
+ } from "../utils";
28
+
29
+ export class PerplexityV1Service {
30
+ private client: OpenAI;
31
+ private working: boolean = true;
32
+
33
+ constructor() {
34
+ this.client = new OpenAI({
35
+ apiKey: PERPLEXITY_API_KEY,
36
+ baseURL: PERPLEXITY_API_BASE_URL,
37
+ });
38
+ logger.info(PERPLEXITY_CLIENT_INITIALIZED_MESSAGE);
39
+ }
40
+
41
+ async createChatCompletion(
42
+ params: IChatCompletionRequest,
43
+ model: string
44
+ ): Promise<IPerplexityResponseChat> {
45
+ const transactionId = generateTransactionId();
46
+ const startTime = new Date();
47
+
48
+ try {
49
+ const { usageMetadata, ...openaiParams } = params;
50
+ const requestParams = {
51
+ ...openaiParams,
52
+ model,
53
+ };
54
+
55
+ const result = (await this.client.chat.completions.create({
56
+ ...requestParams,
57
+ stream: false,
58
+ })) as IPerplexityRawResponse;
59
+
60
+ // Build V1 response format
61
+ const perplexityResponse: IPerplexityResponseChat = {
62
+ responses: [
63
+ {
64
+ text: result.choices[0]?.message?.content || "",
65
+ role: "assistant",
66
+ finishReason: result.choices[0]?.finish_reason,
67
+ },
68
+ ],
69
+ usageMetadata: {
70
+ inputTokenCount: result.usage?.prompt_tokens || 0,
71
+ outputTokenCount: result.usage?.completion_tokens || 0,
72
+ totalTokenCount: result.usage?.total_tokens || 0,
73
+ },
74
+ modelVersion: result.model,
75
+ transactionId,
76
+ };
77
+
78
+ // Send metering data (using current middleware logic)
79
+ if (this.working) {
80
+ const tokenCounts: ITokenCounts = {
81
+ inputTokens: result.usage?.prompt_tokens || 0,
82
+ outputTokens: result.usage?.completion_tokens || 0,
83
+ totalTokens: result.usage?.total_tokens || 0,
84
+ };
85
+ const endTime = new Date();
86
+ logger.info("Metering is working.");
87
+
88
+ const metering = new Metering(
89
+ REVENIUM_METERING_API_KEY ?? "",
90
+ REVENIUM_METERING_BASE_URL ?? ""
91
+ );
92
+
93
+ // Combine user metadata with transaction ID
94
+ const combinedMetadata: IUsageMetadata = {
95
+ ...usageMetadata,
96
+ transactionId,
97
+ };
98
+
99
+ const getMetering = metering.createMetering(
100
+ {
101
+ modelName: model,
102
+ endTime,
103
+ startTime,
104
+ operationType: IOperationType.CHAT,
105
+ stopReason: "END",
106
+ tokenCounts,
107
+ usageMetadata: combinedMetadata,
108
+ },
109
+ false
110
+ );
111
+
112
+ await metering.sendMeteringData(getMetering);
113
+ } else {
114
+ logger.warning("Metering is not working. Check your configuration.");
115
+ }
116
+
117
+ return perplexityResponse;
118
+ } catch (error) {
119
+ logger.error("Error in Perplexity V1 chat completion:", error);
120
+ throw error;
121
+ }
122
+ }
123
+
124
+ async createStreamingCompletion(
125
+ params: IChatCompletionRequest,
126
+ model: string
127
+ ): Promise<IPerplexityStreamingResponse> {
128
+ const transactionId = generateTransactionId();
129
+ const startTime = new Date();
130
+
131
+ try {
132
+ const { usageMetadata, ...openaiParams } = params;
133
+ const requestParams = {
134
+ ...openaiParams,
135
+ model,
136
+ };
137
+
138
+ const stream = await this.client.chat.completions.create({
139
+ ...requestParams,
140
+ stream: true,
141
+ });
142
+
143
+ const wrappedStream = this.wrapStream(
144
+ stream as any,
145
+ transactionId,
146
+ model,
147
+ startTime,
148
+ usageMetadata
149
+ );
150
+
151
+ return {
152
+ stream: wrappedStream,
153
+ usageMetadata: {
154
+ inputTokenCount: 0, // Will be updated when stream completes
155
+ outputTokenCount: 0,
156
+ totalTokenCount: 0,
157
+ },
158
+ modelVersion: model,
159
+ transactionId,
160
+ };
161
+ } catch (error) {
162
+ logger.error("Error in Perplexity V1 streaming:", error);
163
+ throw error;
164
+ }
165
+ }
166
+
167
+ private async *wrapStream(
168
+ stream: AsyncIterable<any>,
169
+ transactionId: string,
170
+ model: string,
171
+ startTime: Date,
172
+ usageMetadata?: IUsageMetadata
173
+ ): AsyncGenerator<IPerplexityStreamChunk> {
174
+ let lastChunk: any = null;
175
+
176
+ try {
177
+ for await (const chunk of stream) {
178
+ lastChunk = chunk;
179
+ yield chunk as IPerplexityStreamChunk;
180
+ }
181
+
182
+ // Send metering data when stream completes (using current middleware logic)
183
+ if (this.working) {
184
+ // For streaming, we'll use the last chunk's usage data if available
185
+ const tokenCounts: ITokenCounts = {
186
+ inputTokens: 0, // Will be updated if lastChunk has usage data
187
+ outputTokens: 0,
188
+ totalTokens: 0,
189
+ };
190
+ const endTime = new Date();
191
+ logger.info("Metering is working.");
192
+
193
+ const metering = new Metering(
194
+ REVENIUM_METERING_API_KEY ?? "",
195
+ REVENIUM_METERING_BASE_URL ?? ""
196
+ );
197
+
198
+ // Combine user metadata with transaction ID
199
+ const combinedMetadata: IUsageMetadata = {
200
+ ...usageMetadata,
201
+ transactionId,
202
+ };
203
+
204
+ const getMetering = metering.createMetering(
205
+ {
206
+ modelName: model,
207
+ endTime,
208
+ startTime,
209
+ operationType: IOperationType.CHAT,
210
+ stopReason: "END",
211
+ tokenCounts,
212
+ usageMetadata: combinedMetadata,
213
+ },
214
+ true
215
+ );
216
+
217
+ await metering.sendMeteringData(getMetering);
218
+ } else {
219
+ logger.warning("Metering is not working. Check your configuration.");
220
+ }
221
+ } catch (error) {
222
+ logger.error("Error in Perplexity V1 stream processing:", error);
223
+ throw error;
224
+ }
225
+ }
226
+
227
+ setWorking(working: boolean) {
228
+ this.working = working;
229
+ }
230
+ }
@@ -0,0 +1,219 @@
1
+ import { logger } from "../models/Logger";
2
+ import { IUsageMetadata } from "../interfaces/usageMetadata";
3
+ import {
4
+ IChatCompletionRequest,
5
+ IPerplexityMessage,
6
+ } from "../interfaces/chatCompletionRequest";
7
+ import { IPerplexityV2Response } from "../interfaces/perplexityResponse";
8
+ import { IPerplexityV2StreamingResponse } from "../interfaces/perplexityStreaming";
9
+ import {
10
+ DEFAULT_CHAT_MODEL,
11
+ PERPLEXITY_MODELS,
12
+ MODEL_CAPABILITIES,
13
+ } from "../utils/constants/perplexityModels";
14
+ import { PerplexityV2Service } from "./perplexityV2.service";
15
+ import { models } from "../utils";
16
+
17
+ export class PerplexityV2Controller {
18
+ private service: PerplexityV2Service;
19
+
20
+ constructor() {
21
+ this.service = new PerplexityV2Service();
22
+ logger.info(
23
+ "Perplexity V2 Controller initialized (Enhanced Response Format)"
24
+ );
25
+ }
26
+
27
+ /**
28
+ * Create a chat completion using Perplexity V2 API (Enhanced Response Format following OpenAI Responses API)
29
+ * @param messages Array of message strings or OpenAI message objects
30
+ * @param model Optional model name (defaults to sonar-pro)
31
+ * @param customMetadata Optional custom metadata for tracking
32
+ * @returns Promise<IPerplexityV2Response>
33
+ */
34
+ async createChat(
35
+ messages: string[] | Array<{ role: string; content: string }>,
36
+ model: string = DEFAULT_CHAT_MODEL,
37
+ customMetadata?: IUsageMetadata
38
+ ): Promise<IPerplexityV2Response> {
39
+ try {
40
+ // Normalize messages to OpenAI format
41
+ const normalizedMessages = this.normalizeMessages(messages);
42
+
43
+ // Validate model
44
+ this.validateModel(model);
45
+
46
+ // Create chat completion request
47
+ const request: IChatCompletionRequest = {
48
+ messages: normalizedMessages,
49
+ usageMetadata: customMetadata,
50
+ };
51
+
52
+ logger.info(
53
+ `Creating Perplexity V2 chat completion with model: ${model}`
54
+ );
55
+
56
+ return await this.service.createChatCompletion(request, model);
57
+ } catch (error) {
58
+ logger.error("Error in Perplexity V2 createChat:", error);
59
+ throw error;
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Create a streaming chat completion using Perplexity V2 API (Enhanced Response Format following OpenAI Responses API)
65
+ * @param messages Array of message strings or OpenAI message objects
66
+ * @param model Optional model name (defaults to sonar-pro)
67
+ * @param customMetadata Optional custom metadata for tracking
68
+ * @returns Promise<IPerplexityV2StreamingResponse>
69
+ */
70
+ async createStreaming(
71
+ messages: string[] | Array<{ role: string; content: string }>,
72
+ model: string = DEFAULT_CHAT_MODEL,
73
+ customMetadata?: IUsageMetadata
74
+ ): Promise<IPerplexityV2StreamingResponse> {
75
+ try {
76
+ // Normalize messages to OpenAI format
77
+ const normalizedMessages = this.normalizeMessages(messages);
78
+
79
+ // Validate model
80
+ this.validateModel(model);
81
+
82
+ // Create streaming request
83
+ const request: IChatCompletionRequest = {
84
+ messages: normalizedMessages,
85
+ usageMetadata: customMetadata,
86
+ };
87
+
88
+ logger.info(
89
+ `Creating Perplexity V2 streaming completion with model: ${model}`
90
+ );
91
+
92
+ return await this.service.createStreamingCompletion(request, model);
93
+ } catch (error) {
94
+ logger.error("Error in Perplexity V2 createStreaming:", error);
95
+ throw error;
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Get available models for Perplexity V2
101
+ * @returns Array of available model names
102
+ */
103
+ getAvailableModels(): string[] {
104
+ return models; // Use the existing models from utils
105
+ }
106
+
107
+ /**
108
+ * Check if a model supports a specific capability
109
+ * @param model Model name
110
+ * @param capability Capability to check (chat, streaming, online)
111
+ * @returns boolean
112
+ */
113
+ supportsCapability(
114
+ model: string,
115
+ capability: "chat" | "streaming" | "online"
116
+ ): boolean {
117
+ return MODEL_CAPABILITIES[model]?.[capability] || false;
118
+ }
119
+
120
+ /**
121
+ * Get model information including capabilities and context window (V2 Enhanced)
122
+ * @param model Model name
123
+ * @returns Enhanced model information object
124
+ */
125
+ getModelInfo(model: string) {
126
+ const capabilities = MODEL_CAPABILITIES[model];
127
+ if (!capabilities) {
128
+ throw new Error(`Model "${model}" is not supported`);
129
+ }
130
+
131
+ return {
132
+ model,
133
+ capabilities: {
134
+ chat: capabilities.chat,
135
+ streaming: capabilities.streaming,
136
+ online: capabilities.online,
137
+ },
138
+ contextWindow: capabilities.contextWindow,
139
+ version: "v2",
140
+ enhanced: true,
141
+ responseFormat: "openai-responses-api",
142
+ };
143
+ }
144
+
145
+ /**
146
+ * Get recommended model for a specific use case (V2 Enhanced)
147
+ * @param useCase Use case (chat, streaming, online-search, offline-chat)
148
+ * @returns Recommended model name with reasoning
149
+ */
150
+ getRecommendedModel(
151
+ useCase: "chat" | "streaming" | "online-search" | "offline-chat"
152
+ ) {
153
+ const recommendations = {
154
+ "online-search": {
155
+ model: PERPLEXITY_MODELS.SONAR_PRO,
156
+ reason: "Best for online search with high accuracy",
157
+ },
158
+ "offline-chat": {
159
+ model: PERPLEXITY_MODELS.LLAMA_3_1_70B,
160
+ reason: "Powerful offline model for complex conversations",
161
+ },
162
+ chat: {
163
+ model: DEFAULT_CHAT_MODEL,
164
+ reason: "Balanced performance for general chat",
165
+ },
166
+ streaming: {
167
+ model: DEFAULT_CHAT_MODEL,
168
+ reason: "Optimized for real-time streaming responses",
169
+ },
170
+ };
171
+
172
+ return recommendations[useCase] || recommendations.chat;
173
+ }
174
+
175
+ /**
176
+ * Get performance metrics for a model (V2 Enhanced)
177
+ * @param model Model name
178
+ * @returns Performance metrics
179
+ */
180
+ getModelPerformance(model: string) {
181
+ const capabilities = MODEL_CAPABILITIES[model];
182
+ if (!capabilities) {
183
+ throw new Error(`Model "${model}" is not supported`);
184
+ }
185
+
186
+ return {
187
+ model,
188
+ contextWindow: capabilities.contextWindow,
189
+ online: capabilities.online,
190
+ estimatedLatency: capabilities.online ? "2-4s" : "1-2s",
191
+ costTier: model.includes("sonar") ? "premium" : "standard",
192
+ version: "v2",
193
+ };
194
+ }
195
+
196
+ private normalizeMessages(
197
+ messages: string[] | Array<{ role: string; content: string }>
198
+ ): IPerplexityMessage[] {
199
+ if (Array.isArray(messages) && typeof messages[0] === "string") {
200
+ // Convert string array to message objects
201
+ return (messages as string[]).map((content, index) => ({
202
+ role: index === 0 ? "user" : "assistant",
203
+ content,
204
+ })) as IPerplexityMessage[];
205
+ }
206
+
207
+ return messages as IPerplexityMessage[];
208
+ }
209
+
210
+ private validateModel(model: string): void {
211
+ if (!models.includes(model)) {
212
+ throw new Error(
213
+ `Model "${model}" is not supported. Available models: ${models.join(
214
+ ", "
215
+ )}`
216
+ );
217
+ }
218
+ }
219
+ }
@@ -0,0 +1,260 @@
1
+ import OpenAI from "openai";
2
+ import { logger } from "../models/Logger";
3
+ import { Metering } from "../models/Metering";
4
+ import { generateTransactionId } from "../utils/generateTransactionId";
5
+ import { IOperationType } from "../interfaces/operation";
6
+ import { IUsageMetadata } from "../interfaces/usageMetadata";
7
+ import { ITokenCounts } from "../interfaces/tokenCounts";
8
+ import {
9
+ IChatCompletionRequest,
10
+ IPerplexityMessage,
11
+ } from "../interfaces/chatCompletionRequest";
12
+ import {
13
+ IPerplexityV2Response,
14
+ IPerplexityRawResponse,
15
+ } from "../interfaces/perplexityResponse";
16
+ import {
17
+ IPerplexityV2StreamingResponse,
18
+ IPerplexityV2StreamChunk,
19
+ } from "../interfaces/perplexityStreaming";
20
+ import {
21
+ PERPLEXITY_API_BASE_URL,
22
+ PERPLEXITY_API_KEY,
23
+ PERPLEXITY_CLIENT_INITIALIZED_MESSAGE,
24
+ REVENIUM_METERING_API_KEY,
25
+ REVENIUM_METERING_BASE_URL,
26
+ } from "../utils";
27
+
28
+ export class PerplexityV2Service {
29
+ private client: OpenAI;
30
+ private working: boolean = true;
31
+
32
+ constructor() {
33
+ this.client = new OpenAI({
34
+ apiKey: PERPLEXITY_API_KEY,
35
+ baseURL: PERPLEXITY_API_BASE_URL,
36
+ });
37
+ logger.info(`${PERPLEXITY_CLIENT_INITIALIZED_MESSAGE} (V2)`);
38
+ }
39
+
40
+ async createChatCompletion(
41
+ params: IChatCompletionRequest,
42
+ model: string
43
+ ): Promise<IPerplexityV2Response> {
44
+ const transactionId = generateTransactionId();
45
+ const startTime = Date.now();
46
+
47
+ try {
48
+ const { usageMetadata, ...openaiParams } = params;
49
+ const requestParams = {
50
+ ...openaiParams,
51
+ model,
52
+ };
53
+
54
+ const result = (await this.client.chat.completions.create({
55
+ ...requestParams,
56
+ stream: false,
57
+ })) as IPerplexityRawResponse;
58
+
59
+ const endTime = Date.now();
60
+ const processingTime = endTime - startTime;
61
+
62
+ // Build V2 enhanced response format (following OpenAI Responses API)
63
+ const perplexityV2Response: IPerplexityV2Response = {
64
+ id: result.id,
65
+ object: "chat.completion",
66
+ created: result.created,
67
+ model: result.model,
68
+ choices: result.choices,
69
+ usage: {
70
+ prompt_tokens: result.usage?.prompt_tokens || 0,
71
+ completion_tokens: result.usage?.completion_tokens || 0,
72
+ total_tokens: result.usage?.total_tokens || 0,
73
+ },
74
+ metadata: {
75
+ transactionId,
76
+ processingTime,
77
+ version: "v2",
78
+ },
79
+ };
80
+
81
+ // Send metering data (modernized approach)
82
+ await this.sendMeteringData(
83
+ transactionId,
84
+ model,
85
+ IOperationType.CHAT,
86
+ {
87
+ inputTokenCount: perplexityV2Response.usage.prompt_tokens,
88
+ outputTokenCount: perplexityV2Response.usage.completion_tokens,
89
+ totalTokenCount: perplexityV2Response.usage.total_tokens,
90
+ },
91
+ processingTime,
92
+ usageMetadata
93
+ );
94
+
95
+ return perplexityV2Response;
96
+ } catch (error) {
97
+ logger.error("Error in Perplexity V2 chat completion:", error);
98
+ throw error;
99
+ }
100
+ }
101
+
102
+ async createStreamingCompletion(
103
+ params: IChatCompletionRequest,
104
+ model: string
105
+ ): Promise<IPerplexityV2StreamingResponse> {
106
+ const transactionId = generateTransactionId();
107
+ const startTime = Date.now();
108
+
109
+ try {
110
+ const { usageMetadata, ...openaiParams } = params;
111
+ const requestParams = {
112
+ ...openaiParams,
113
+ model,
114
+ };
115
+
116
+ const stream = await this.client.chat.completions.create({
117
+ ...requestParams,
118
+ stream: true,
119
+ });
120
+
121
+ const wrappedStream = this.wrapStreamV2(
122
+ stream as any,
123
+ transactionId,
124
+ model,
125
+ startTime,
126
+ usageMetadata
127
+ );
128
+
129
+ return {
130
+ id: transactionId,
131
+ object: "chat.completion.chunk",
132
+ stream: wrappedStream,
133
+ metadata: {
134
+ transactionId,
135
+ startTime,
136
+ version: "v2",
137
+ },
138
+ };
139
+ } catch (error) {
140
+ logger.error("Error in Perplexity V2 streaming:", error);
141
+ throw error;
142
+ }
143
+ }
144
+
145
+ private async *wrapStreamV2(
146
+ stream: AsyncIterable<any>,
147
+ transactionId: string,
148
+ model: string,
149
+ startTime: number,
150
+ usageMetadata?: IUsageMetadata
151
+ ): AsyncGenerator<IPerplexityV2StreamChunk> {
152
+ let lastChunk: any = null;
153
+
154
+ try {
155
+ for await (const chunk of stream) {
156
+ lastChunk = chunk;
157
+
158
+ // Transform to V2 format (following OpenAI Responses API)
159
+ const v2Chunk: IPerplexityV2StreamChunk = {
160
+ id: chunk.id,
161
+ object: "chat.completion.chunk",
162
+ created: chunk.created,
163
+ model: chunk.model,
164
+ choices: chunk.choices,
165
+ usage: chunk.usage
166
+ ? {
167
+ prompt_tokens: chunk.usage.prompt_tokens || 0,
168
+ completion_tokens: chunk.usage.completion_tokens || 0,
169
+ total_tokens: chunk.usage.total_tokens || 0,
170
+ }
171
+ : undefined,
172
+ };
173
+
174
+ yield v2Chunk;
175
+ }
176
+
177
+ // Send metering data when stream completes
178
+ const endTime = Date.now();
179
+ const processingTime = endTime - startTime;
180
+
181
+ if (lastChunk?.usage) {
182
+ await this.sendMeteringData(
183
+ transactionId,
184
+ model,
185
+ IOperationType.CHAT,
186
+ {
187
+ inputTokenCount: lastChunk.usage.prompt_tokens || 0,
188
+ outputTokenCount: lastChunk.usage.completion_tokens || 0,
189
+ totalTokenCount: lastChunk.usage.total_tokens || 0,
190
+ },
191
+ processingTime,
192
+ usageMetadata
193
+ );
194
+ }
195
+ } catch (error) {
196
+ logger.error("Error in Perplexity V2 stream processing:", error);
197
+ throw error;
198
+ }
199
+ }
200
+
201
+ private async sendMeteringData(
202
+ transactionId: string,
203
+ model: string,
204
+ operationType: IOperationType,
205
+ usageData: IUsageMetadata,
206
+ processingTime: number,
207
+ customMetadata?: IUsageMetadata
208
+ ): Promise<void> {
209
+ if (!this.working) {
210
+ logger.warning("Metering is not working. Check your configuration.");
211
+ return;
212
+ }
213
+
214
+ try {
215
+ const startTime = new Date(Date.now() - processingTime);
216
+ const endTime = new Date();
217
+
218
+ const metering = new Metering(
219
+ REVENIUM_METERING_API_KEY ?? "",
220
+ REVENIUM_METERING_BASE_URL ?? ""
221
+ );
222
+
223
+ // Convert IUsageMetadata to ITokenCounts for metering
224
+ const tokenCounts: ITokenCounts = {
225
+ inputTokens: usageData.inputTokenCount || 0,
226
+ outputTokens: usageData.outputTokenCount || 0,
227
+ totalTokens: usageData.totalTokenCount || 0,
228
+ };
229
+
230
+ // Combine custom metadata with transaction ID (token counts are handled separately)
231
+ const combinedMetadata: IUsageMetadata = {
232
+ ...customMetadata,
233
+ transactionId,
234
+ };
235
+
236
+ // Use the same approach as V1 but with V2 enhancements
237
+ const meteringData = metering.createMetering(
238
+ {
239
+ modelName: model,
240
+ endTime,
241
+ startTime,
242
+ operationType,
243
+ stopReason: "END",
244
+ tokenCounts,
245
+ usageMetadata: combinedMetadata,
246
+ },
247
+ false
248
+ );
249
+
250
+ await metering.sendMeteringData(meteringData);
251
+ logger.info("V2 Metering data sent successfully.");
252
+ } catch (error) {
253
+ logger.error("Error sending V2 metering data:", error);
254
+ }
255
+ }
256
+
257
+ setWorking(working: boolean) {
258
+ this.working = working;
259
+ }
260
+ }