@revenium/perplexity
Version:
NodeJS middleware for Perplexity AI API with Revenium metering
126 lines • 4.9 kB
JavaScript
/**
* Revenium Metering Module
* Handles tracking and sending metering data to Revenium
*/
import { getReveniumConfig, isReveniumEnabled, } from "../config/revenium-config";
import { logger } from "../../utils/logger";
/**
* Generate a unique transaction ID
*/
export function generateTransactionId() {
return `txn_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`;
}
/**
* Format timestamp to ISO string
*/
function formatTimestamp(date) {
return date.toISOString();
}
/**
* Calculate duration in milliseconds
*/
function calculateDuration(startTime, endTime) {
return endTime.getTime() - startTime.getTime();
}
/**
* Build metering data from request information
*/
export function buildMeteringData(params) {
const { model, startTime, endTime, inputTokens, outputTokens, totalTokens, reasoningTokens = 0, cachedTokens = 0, transactionId, isStreamed, stopReason, usageMetadata = {}, } = params;
const config = getReveniumConfig();
const agent = "perplexity";
const costType = "TOKEN";
const operationType = "CHAT";
// Build subscriber information
const subscriberId = usageMetadata.subscriber?.id ||
usageMetadata.subscriberId ||
`user-${generateTransactionId()}`;
const subscriberEmail = usageMetadata.subscriber?.email ||
usageMetadata.subscriberEmail ||
`user@${agent}.ai`;
const subscriberCredential = usageMetadata.subscriber?.credential ||
(usageMetadata.subscriberCredentialName &&
usageMetadata.subscriberCredential
? {
name: usageMetadata.subscriberCredentialName,
value: usageMetadata.subscriberCredential,
}
: {
name: "default",
value: "default-credential",
});
const subscriber = {
id: subscriberId,
email: subscriberEmail,
credential: subscriberCredential,
};
// Build the payload in the exact order expected by Revenium API v2
const payload = {
stopReason: "END",
costType: "AI", // Fixed value as per Google middleware
isStreamed,
taskType: "AI", // Fixed value as per Google middleware
agent: usageMetadata.agent || agent,
operationType: usageMetadata.operationType || operationType,
inputTokenCount: usageMetadata.inputTokenCount ?? inputTokens,
outputTokenCount: usageMetadata.outputTokenCount ?? outputTokens,
reasoningTokenCount: usageMetadata.reasoningTokenCount ?? reasoningTokens,
cacheCreationTokenCount: usageMetadata.cacheCreationTokenCount ?? cachedTokens,
cacheReadTokenCount: usageMetadata.cacheReadTokenCount ?? 0,
totalTokenCount: usageMetadata.totalTokenCount ?? totalTokens,
organizationId: usageMetadata.organizationId || "my-customers-name",
productId: usageMetadata.productId || "free-trial",
subscriber,
model,
transactionId: usageMetadata.transactionId || transactionId,
responseTime: usageMetadata.responseTime || formatTimestamp(endTime),
requestDuration: calculateDuration(startTime, endTime),
provider: agent,
requestTime: usageMetadata.requestTime || formatTimestamp(startTime),
completionStartTime: usageMetadata.completionStartTime || formatTimestamp(endTime),
timeToFirstToken: usageMetadata.timeToFirstToken || 0,
middleware_source: "node",
};
// Only add traceId if it's provided
if (usageMetadata.traceId) {
payload.traceId = usageMetadata.traceId;
}
return payload;
}
/**
* Send metering data to Revenium
*/
export async function sendMeteringData(meteringData) {
if (!isReveniumEnabled()) {
logger.debug("Revenium metering is disabled, skipping metering data");
return;
}
const config = getReveniumConfig();
if (!config) {
logger.warn("Revenium configuration not initialized");
return;
}
try {
const url = `${config.meteringBaseUrl}/v2/ai/completions`;
logger.debug("Sending metering data to Revenium:", JSON.stringify(meteringData, null, 2));
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": config.meteringApiKey,
accept: "application/json",
},
body: JSON.stringify(meteringData),
});
if (!response.ok) {
const errorData = await response.text();
logger.error(`Metering API request failed with status ${response.status}: ${errorData}`);
return;
}
logger.info("Metering data sent successfully to Revenium");
}
catch (error) {
logger.error("Error sending metering data:", error.message);
}
}
//# sourceMappingURL=metering.js.map