/**
 * Token Counter with Lazy Loading
 *
 * Tiktoken is lazy-loaded on first use to reduce startup time (23 MB module).
 * The module is loaded synchronously when first needed, but the application
 * can start without it.
 */
// Lazy-loaded tiktoken module
let tiktoken = null;
let loadAttempted = false;
/**
 * Lazily load tiktoken module (synchronous after first load)
 */
function getTiktoken() {
    if (tiktoken)
        return tiktoken;
    if (loadAttempted)
        return null;
    loadAttempted = true;
    try {
        // Dynamic require for lazy loading
        tiktoken = require('tiktoken');
    }
    catch {
        // Tiktoken not available, will use estimation
        tiktoken = null;
    }
    return tiktoken;
}
export class TokenCounter {
    encoder = null;
    model;
    initialized = false;
    constructor(model = 'gpt-4') {
        this.model = model;
    }
    /**
     * Initialize the encoder lazily
     */
    ensureInitialized() {
        if (this.initialized)
            return;
        this.initialized = true;
        const tk = getTiktoken();
        if (!tk)
            return;
        try {
            // Try to get encoding for specific model
            this.encoder = tk.encoding_for_model(this.model);
        }
        catch {
            // Fallback to cl100k_base (used by GPT-4 and most modern models)
            this.encoder = tk.get_encoding('cl100k_base');
        }
    }
    /**
     * Count tokens in a string
     */
    countTokens(text) {
        if (!text)
            return 0;
        this.ensureInitialized();
        if (this.encoder) {
            return this.encoder.encode(text).length;
        }
        // Fallback: estimate ~4 chars per token
        return Math.ceil(text.length / 4);
    }
    /**
     * Count tokens in messages array (for chat completions)
     */
    countMessageTokens(messages) {
        let totalTokens = 0;
        for (const message of messages) {
            // Every message follows <|start|>{role/name}\n{content}<|end|\>\n
            totalTokens += 3; // Base tokens per message
            if (message.content && typeof message.content === 'string') {
                totalTokens += this.countTokens(message.content);
            }
            if (message.role) {
                totalTokens += this.countTokens(message.role);
            }
            // Add extra tokens for tool calls if present
            if (message.tool_calls) {
                totalTokens += this.countTokens(JSON.stringify(message.tool_calls));
            }
        }
        totalTokens += 3; // Every reply is primed with <|start|>assistant<|message|>
        return totalTokens;
    }
    /**
     * Estimate tokens for streaming content
     * This is an approximation since we don't have the full response yet
     */
    estimateStreamingTokens(accumulatedContent) {
        return this.countTokens(accumulatedContent);
    }
    /**
     * Clean up resources
     */
    dispose() {
        if (this.encoder) {
            this.encoder.free();
            this.encoder = null;
        }
        this.initialized = false;
    }
}
/**
 * Format token count for display (e.g., 1.2k for 1200)
 */
export function formatTokenCount(count) {
    if (count <= 999) {
        return count.toString();
    }
    if (count < 1_000_000) {
        const k = count / 1000;
        return k % 1 === 0 ? `${k}k` : `${k.toFixed(1)}k`;
    }
    const m = count / 1_000_000;
    return m % 1 === 0 ? `${m}m` : `${m.toFixed(1)}m`;
}
/**
 * Create a token counter instance
 */
export function createTokenCounter(model) {
    return new TokenCounter(model);
}
// Singleton instance for simple usage
let defaultCounter = null;
/**
 * Count tokens in a string using default encoder
 * This is a convenience function for simple token counting
 *
 * @param text - The text to count tokens for
 * @returns Number of tokens
 *
 * @example
 * ```typescript
 * const count = countTokens('Hello, world!');
 * console.log(count); // 4
 * ```
 */
export function countTokens(text) {
    if (!defaultCounter) {
        defaultCounter = new TokenCounter();
    }
    return defaultCounter.countTokens(text);
}
/**
 * Estimate tokens without loading tiktoken (fast approximation)
 * ~4 characters per token on average
 */
export function estimateTokens(text) {
    if (!text)
        return 0;
    return Math.ceil(text.length / 4);
}
/**
 * Preload tiktoken module in background (optional)
 * Call this early to warm up the module without blocking
 */
export function preloadTiktoken() {
    setTimeout(() => {
        getTiktoken();
    }, 100);
}
// ============================================================================
// Cost Calculation
// ============================================================================
/**
 * Token pricing per 1K tokens (in USD)
 */
export const TOKEN_PRICING = {
    // OpenAI
    'gpt-4o': { input: 0.0025, output: 0.01 },
    'gpt-4o-mini': { input: 0.00015, output: 0.0006 },
    'gpt-4-turbo': { input: 0.01, output: 0.03 },
    'gpt-4': { input: 0.03, output: 0.06 },
    'gpt-3.5-turbo': { input: 0.0005, output: 0.0015 },
    // Anthropic
    'claude-3-opus': { input: 0.015, output: 0.075 },
    'claude-3-sonnet': { input: 0.003, output: 0.015 },
    'claude-3-haiku': { input: 0.00025, output: 0.00125 },
    'claude-3.5-sonnet': { input: 0.003, output: 0.015 },
    // xAI Grok
    'grok-2': { input: 0.002, output: 0.01 },
    'grok-2-mini': { input: 0.0002, output: 0.001 },
    'grok-3': { input: 0.003, output: 0.015 },
    'grok-3-mini': { input: 0.0003, output: 0.0015 },
    // Local models (free)
    'local': { input: 0, output: 0 },
    'ollama': { input: 0, output: 0 },
    'lmstudio': { input: 0, output: 0 },
};
/**
 * Calculate cost for a request
 */
export function calculateCost(inputTokens, outputTokens, model) {
    // Find pricing (check for partial match)
    let pricing = TOKEN_PRICING[model];
    if (!pricing) {
        // Try to match by prefix
        const lowerModel = model.toLowerCase();
        for (const [key, value] of Object.entries(TOKEN_PRICING)) {
            if (lowerModel.includes(key.toLowerCase())) {
                pricing = value;
                break;
            }
        }
    }
    // Default to local (free) if not found
    if (!pricing) {
        pricing = TOKEN_PRICING['local'];
    }
    const inputCost = (inputTokens / 1000) * pricing.input;
    const outputCost = (outputTokens / 1000) * pricing.output;
    return {
        inputCost,
        outputCost,
        totalCost: inputCost + outputCost,
    };
}
/**
 * Format cost for display
 */
export function formatCost(cost) {
    if (cost === 0)
        return 'Free';
    if (cost < 0.001)
        return `$${(cost * 1000).toFixed(3)}m`; // millicents
    if (cost < 0.01)
        return `$${cost.toFixed(4)}`;
    if (cost < 1)
        return `$${cost.toFixed(3)}`;
    return `$${cost.toFixed(2)}`;
}
//# sourceMappingURL=token-counter.js.map