Skip to main content
Glama
tokenManager.js6.2 kB
import { encode } from 'gpt-3-encoder'; export class TokenManager { constructor(options = {}) { this.maxTokens = options.maxTokens || 4000; this.approximationRatio = 1.3; // Average tokens per word } // Accurate token counting using GPT-3 encoder countTokens(text) { try { return encode(text).length; } catch (error) { // Fallback to approximation if encoding fails return this.approximateTokens(text); } } // Fast approximation for quick estimates approximateTokens(text) { const words = text.split(/\s+/).length; return Math.ceil(words * this.approximationRatio); } // Check if text is within token limit isWithinLimit(text, limit = this.maxTokens) { return this.countTokens(text) <= limit; } // Truncate text to fit within token limit truncateToLimit(text, limit = this.maxTokens, options = {}) { const { preserveStructure = true, addEllipsis = true, priority = 'start' // 'start', 'end', or 'balanced' } = options; const currentTokens = this.countTokens(text); if (currentTokens <= limit) { return text; } // Calculate how much to keep const ratio = limit / currentTokens; const estimatedLength = Math.floor(text.length * ratio * 0.9); // 90% to be safe let truncated; switch (priority) { case 'end': truncated = text.substring(text.length - estimatedLength); if (addEllipsis) truncated = '...' + truncated; break; case 'balanced': const halfLength = Math.floor(estimatedLength / 2); const start = text.substring(0, halfLength); const end = text.substring(text.length - halfLength); truncated = start + (addEllipsis ? '\n...[truncated]...\n' : '\n') + end; break; case 'start': default: truncated = text.substring(0, estimatedLength); if (addEllipsis) truncated += '...'; } // Fine-tune to ensure we're under the limit while (this.countTokens(truncated) > limit && truncated.length > 10) { truncated = truncated.substring(0, truncated.length - 10); } return truncated; } // Smart truncation for structured content (JSON, code, etc.) smartTruncate(content, limit = this.maxTokens) { if (typeof content === 'string') { return this.truncateToLimit(content, limit); } if (typeof content === 'object') { return this.truncateObject(content, limit); } return String(content); } // Truncate objects intelligently truncateObject(obj, limit) { const priorityKeys = ['summary', 'description', 'title', 'name', 'error', 'message']; const deprioritizeKeys = ['metadata', 'debug', 'raw', 'internal']; // Start with high-priority fields let result = {}; let currentTokens = 2; // Account for {} // Add priority fields first for (const key of priorityKeys) { if (obj[key] !== undefined) { const value = this.truncateValue(obj[key], Math.floor(limit * 0.3)); const valueTokens = this.countTokens(JSON.stringify({ [key]: value })); if (currentTokens + valueTokens <= limit) { result[key] = value; currentTokens += valueTokens; } } } // Add other fields for (const [key, value] of Object.entries(obj)) { if (!priorityKeys.includes(key) && !deprioritizeKeys.includes(key)) { const truncatedValue = this.truncateValue(value, Math.floor((limit - currentTokens) * 0.5)); const valueTokens = this.countTokens(JSON.stringify({ [key]: truncatedValue })); if (currentTokens + valueTokens <= limit) { result[key] = truncatedValue; currentTokens += valueTokens; } } } // Add deprioritized fields if space remains for (const key of deprioritizeKeys) { if (obj[key] !== undefined && currentTokens < limit * 0.9) { const remainingTokens = limit - currentTokens; const value = this.truncateValue(obj[key], Math.floor(remainingTokens * 0.8)); const valueTokens = this.countTokens(JSON.stringify({ [key]: value })); if (currentTokens + valueTokens <= limit) { result[key] = value; currentTokens += valueTokens; } } } return result; } // Truncate individual values based on type truncateValue(value, limit) { if (typeof value === 'string') { return this.truncateToLimit(value, limit); } if (Array.isArray(value)) { const itemLimit = Math.floor(limit / Math.max(value.length, 1)); const truncated = value .slice(0, Math.min(value.length, 10)) // Max 10 items .map(item => this.truncateValue(item, itemLimit)); if (value.length > truncated.length) { truncated.push(`... and ${value.length - truncated.length} more items`); } return truncated; } if (typeof value === 'object' && value !== null) { return this.truncateObject(value, limit); } return value; } // Split large content into chunks splitIntoChunks(text, chunkSize = this.maxTokens) { const chunks = []; const lines = text.split('\n'); let currentChunk = ''; let currentTokens = 0; for (const line of lines) { const lineTokens = this.approximateTokens(line); if (currentTokens + lineTokens > chunkSize && currentChunk) { chunks.push(currentChunk.trim()); currentChunk = ''; currentTokens = 0; } currentChunk += line + '\n'; currentTokens += lineTokens; } if (currentChunk) { chunks.push(currentChunk.trim()); } return chunks; } // Get token budget information getTokenBudget(used, total = this.maxTokens) { return { used, total, remaining: total - used, percentage: Math.round((used / total) * 100), withinBudget: used <= total }; } } // Singleton instance let tokenManagerInstance = null; export function getTokenManager(options) { if (!tokenManagerInstance) { tokenManagerInstance = new TokenManager(options); } return tokenManagerInstance; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/dvillegastech/flutter_mcp_2'

If you have feedback or need assistance with the MCP directory API, please join our Discord server