UNPKG

@juspay/neurolink

Version:

Universal AI Development Platform with working MCP integration, multi-provider support, voice (TTS/STT/realtime), and professional CLI. 58+ external MCP servers discoverable, multimodal file processing, RAG pipelines. Build, test, and deploy AI applicatio

240 lines 8.28 kB
/** * Centralized token usage extraction utilities * Handles multiple provider formats and optional fields * * Consolidates token extraction logic from: * - GenerationHandler.ts * - analytics.ts * - streamAnalytics.ts */ /** * Extract input token count from various provider formats * Priority: input > inputTokens > promptTokens */ export function extractInputTokens(usage) { if (typeof usage.input === "number") { return usage.input; } if (typeof usage.inputTokens === "number") { return usage.inputTokens; } if (typeof usage.promptTokens === "number") { return usage.promptTokens; } return 0; } /** * Extract output token count from various provider formats * Priority: output > outputTokens > completionTokens */ export function extractOutputTokens(usage) { if (typeof usage.output === "number") { return usage.output; } if (typeof usage.outputTokens === "number") { return usage.outputTokens; } if (typeof usage.completionTokens === "number") { return usage.completionTokens; } return 0; } /** * Extract total token count from various provider formats * Falls back to input + output if total is not provided */ export function extractTotalTokens(usage, input, output) { if (typeof usage.total === "number") { return usage.total; } if (typeof usage.totalTokens === "number") { return usage.totalTokens; } return input + output; } /** * Extract reasoning/thinking token count from various provider formats * Supports: reasoningTokens, thinkingTokens, reasoning_tokens, reasoning */ export function extractReasoningTokens(usage) { if (typeof usage.reasoningTokens === "number" && usage.reasoningTokens > 0) { return usage.reasoningTokens; } if (typeof usage.thinkingTokens === "number" && usage.thinkingTokens > 0) { return usage.thinkingTokens; } if (typeof usage.reasoning_tokens === "number" && usage.reasoning_tokens > 0) { return usage.reasoning_tokens; } if (typeof usage.reasoning === "number" && usage.reasoning > 0) { return usage.reasoning; } return undefined; } /** * Extract cache creation token count from various provider formats * Supports: cacheCreationInputTokens, cacheCreationTokens */ export function extractCacheCreationTokens(usage) { if (typeof usage.cacheCreationInputTokens === "number" && usage.cacheCreationInputTokens > 0) { return usage.cacheCreationInputTokens; } if (typeof usage.cacheCreationTokens === "number" && usage.cacheCreationTokens > 0) { return usage.cacheCreationTokens; } return undefined; } /** * Extract cache read token count from various provider formats * Supports: cacheReadInputTokens, cacheReadTokens */ export function extractCacheReadTokens(usage) { if (typeof usage.cacheReadInputTokens === "number" && usage.cacheReadInputTokens > 0) { return usage.cacheReadInputTokens; } if (typeof usage.cacheReadTokens === "number" && usage.cacheReadTokens > 0) { return usage.cacheReadTokens; } return undefined; } /** * Calculate cache savings percentage * * This represents the percentage of input tokens served from cache. * For Anthropic, cache read tokens cost 0.1x, so actual cost savings = cacheSavingsPercent * 0.9 * For other providers, cost savings may vary based on their cache pricing. * * @param cacheReadTokens Number of tokens read from cache * @param inputTokens Number of non-cached input tokens * @returns Percentage of tokens served from cache (0-100), or undefined if no cache usage */ export function calculateCacheSavingsPercent(cacheReadTokens, inputTokens) { if (cacheReadTokens === undefined || cacheReadTokens <= 0) { return undefined; } const totalInputWithCache = inputTokens + cacheReadTokens; if (totalInputWithCache <= 0) { return undefined; } return Math.round((cacheReadTokens / totalInputWithCache) * 100); } /** * Extract token usage from various provider response formats * * Handles multiple input formats: * - BaseProvider normalized format (input/output/total) * - AI SDK format (inputTokens/outputTokens/totalTokens) * - OpenAI/Mistral format (promptTokens/completionTokens) * - Nested usage objects * * Also extracts optional fields: * - Cache creation and read tokens (Anthropic-style) * - Reasoning/thinking tokens (OpenAI o1, Anthropic, Google) * - Cache savings percentage * * @param result Raw usage object from provider response * @param options Extraction options * @returns Normalized TokenUsage object */ export function extractTokenUsage(result, options = {}) { const { calculateCacheSavings = true, missingOptionalBehavior = "undefined", } = options; // Handle null/undefined input if (!result) { return { input: 0, output: 0, total: 0 }; } // Handle nested usage object (some providers wrap usage in a usage property) const usage = result.usage && typeof result.usage === "object" ? result.usage : result; // Extract base token counts const input = extractInputTokens(usage); const output = extractOutputTokens(usage); const total = extractTotalTokens(usage, input, output); // Extract optional token fields const reasoning = extractReasoningTokens(usage); const cacheCreationTokens = extractCacheCreationTokens(usage); const cacheReadTokens = extractCacheReadTokens(usage); // Calculate cache savings if enabled const cacheSavingsPercent = calculateCacheSavings ? calculateCacheSavingsPercent(cacheReadTokens, input) : undefined; // Build result object const tokenUsage = { input, output, total, }; // Add optional fields based on behavior setting if (missingOptionalBehavior === "zero") { tokenUsage.cacheCreationTokens = cacheCreationTokens ?? 0; tokenUsage.cacheReadTokens = cacheReadTokens ?? 0; tokenUsage.reasoning = reasoning ?? 0; tokenUsage.cacheSavingsPercent = cacheSavingsPercent ?? 0; } else { // Only include optional fields if they have values if (cacheCreationTokens !== undefined) { tokenUsage.cacheCreationTokens = cacheCreationTokens; } if (cacheReadTokens !== undefined) { tokenUsage.cacheReadTokens = cacheReadTokens; } if (reasoning !== undefined) { tokenUsage.reasoning = reasoning; } if (cacheSavingsPercent !== undefined) { tokenUsage.cacheSavingsPercent = cacheSavingsPercent; } } return tokenUsage; } /** * Create a default/empty TokenUsage object * Useful for error handling and fallback scenarios */ export function createEmptyTokenUsage() { return { input: 0, output: 0, total: 0, }; } /** * Check if a TokenUsage object has any non-zero values */ export function hasTokenUsage(usage) { return usage.input > 0 || usage.output > 0 || usage.total > 0; } /** * Merge two TokenUsage objects by summing their values * Useful for aggregating usage across multiple calls */ export function mergeTokenUsage(a, b) { const merged = { input: a.input + b.input, output: a.output + b.output, total: a.total + b.total, }; // Merge optional fields if present in either const cacheCreationTokens = (a.cacheCreationTokens ?? 0) + (b.cacheCreationTokens ?? 0); const cacheReadTokens = (a.cacheReadTokens ?? 0) + (b.cacheReadTokens ?? 0); const reasoning = (a.reasoning ?? 0) + (b.reasoning ?? 0); if (cacheCreationTokens > 0) { merged.cacheCreationTokens = cacheCreationTokens; } if (cacheReadTokens > 0) { merged.cacheReadTokens = cacheReadTokens; } if (reasoning > 0) { merged.reasoning = reasoning; } // Recalculate cache savings for merged usage const cacheSavingsPercent = calculateCacheSavingsPercent(merged.cacheReadTokens, merged.input); if (cacheSavingsPercent !== undefined) { merged.cacheSavingsPercent = cacheSavingsPercent; } return merged; } //# sourceMappingURL=tokenUtils.js.map