spb/vquant Public MIT
VibeQuant — AI-powered institutional-grade financial intelligence platform.
TypeScript 84.3%
Python 11.7%
JavaScript 1.6%
CSS 1.5%
HTML 0.7%
1/*2 * =============================================================================3 * VibeQuant (vquant) — AI-Powered Financial Intelligence Platform4 * -----------------------------------------------------------------------------5 * File: server/services/claude/tokenManagement.ts6 *7 * Author: Simon-Pierre Boucher8 * Contact: contact@spboucher.ai9 * Website: https://www.spboucher.ai10 * Demo: https://www.vquant.ai11 * License: MIT (see LICENSE)12 *13 * Copyright © 2026 Simon-Pierre Boucher. All rights reserved.14 * =============================================================================15 */1617import { encode } from 'gpt-tokenizer';18import axios from 'axios';1920const ANTHROPIC_API_URL = 'https://api.anthropic.com/v1/messages';21const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;22const ANTHROPIC_VERSION = '2023-06-01';23const DEFAULT_MODEL = 'claude-fable-5';2425// Estimate token count (rough approximation: 1 token ≈ 4 characters)26export function estimateTokens(text: string): number {27 return Math.ceil(text.length / 4);28}2930// Estimate tokens in messages array31export function estimateMessagesTokens(messages: any[]): number {32 let total = 0;33 for (const msg of messages) {34 if (typeof msg.content === 'string') {35 total += estimateTokens(msg.content);36 } else if (Array.isArray(msg.content)) {37 for (const block of msg.content) {38 if (block.type === 'text' && block.text) {39 total += estimateTokens(block.text);40 } else if (block.type === 'tool_result' && block.content) {41 if (typeof block.content === 'string') {42 total += estimateTokens(block.content);43 } else if (Array.isArray(block.content)) {44 for (const item of block.content) {45 if (item.type === 'text' && item.text) {46 total += estimateTokens(item.text);47 }48 }49 }50 }51 }52 }53 }54 return total;55}5657// Extract text content from messages, removing large tool results58export function extractTextContent(messages: any[]): string {59 let textContent = '';6061 for (const msg of messages) {62 if (msg.role === 'user') {63 if (typeof msg.content === 'string') {64 textContent += `User: ${msg.content}\n\n`;65 }66 } else if (msg.role === 'assistant') {67 if (typeof msg.content === 'string') {68 textContent += `Assistant: ${msg.content}\n\n`;69 } else if (Array.isArray(msg.content)) {70 // Extract only text blocks, skip tool_use blocks71 for (const block of msg.content) {72 if (block.type === 'text' && block.text) {73 textContent += `Assistant: ${block.text}\n\n`;74 }75 }76 }77 }78 }7980 // Truncate if still too long (keep last 10000 chars)81 if (textContent.length > 10000) {82 textContent = '...[earlier conversation truncated]...\n\n' + textContent.slice(-10000);83 }8485 return textContent;86}8788// Summarize old conversation history89export async function summarizeHistory(messages: any[], model: string = DEFAULT_MODEL): Promise<string> {90 try {91 const textContent = extractTextContent(messages);9293 const response = await axios.post(94 ANTHROPIC_API_URL,95 {96 model,97 max_tokens: 2000,98 temperature: 0.5,99 stream: false,100 system: 'You are a helpful assistant that summarizes conversation history. Create a concise but comprehensive summary of the key points, decisions, and context from the conversation history provided. Focus on financial queries, analysis topics, and important context.',101 messages: [102 {103 role: 'user',104 content: `Please summarize the following conversation history, focusing on key financial queries and analysis topics:\n\n${textContent}`105 }106 ],107 },108 {109 headers: {110 'Content-Type': 'application/json',111 'x-api-key': ANTHROPIC_API_KEY!,112 'anthropic-version': ANTHROPIC_VERSION,113 },114 }115 );116117 return response.data.content[0].text;118 } catch (error) {119 console.error('Error summarizing history:', error);120 const textContent = extractTextContent(messages);121 return `Previous conversation summary (${messages.length} messages):\n${textContent.slice(0, 500)}...`;122 }123}124125// Truncate tool results to avoid oversized messages126export function truncateToolResults(messages: any[], maxLength: number = 5000): any[] {127 return messages.map(msg => {128 if (!Array.isArray(msg.content)) return msg;129130 const truncatedContent = msg.content.map((block: any) => {131 if (block.type === 'tool_result' && block.content) {132 if (typeof block.content === 'string' && block.content.length > maxLength) {133 return {134 ...block,135 content: block.content.slice(0, maxLength) + '\n... [truncated for length]'136 };137 }138 if (Array.isArray(block.content)) {139 const textItems = block.content.filter((item: any) => item.type === 'text');140 if (textItems.length > 0) {141 let totalLength = 0;142 const truncatedItems = [];143 for (const item of textItems) {144 if (totalLength + item.text.length > maxLength) {145 truncatedItems.push({146 type: 'text',147 text: item.text.slice(0, maxLength - totalLength) + '\n... [truncated]'148 });149 break;150 }151 truncatedItems.push(item);152 totalLength += item.text.length;153 }154 return { ...block, content: truncatedItems };155 }156 }157 }158 return block;159 });160161 return { ...msg, content: truncatedContent };162 });163}164165// Calculate cost based on token usage166export function calculateCost(inputTokens: number, outputTokens: number): number {167 const inputCostPer1M = 3.00;168 const outputCostPer1M = 15.00;169 const inputCost = (inputTokens / 1_000_000) * inputCostPer1M;170 const outputCost = (outputTokens / 1_000_000) * outputCostPer1M;171 return inputCost + outputCost;172}173