UNPKG

image-token-meter

Version:

Calculate token consumption for images in OpenAI Vision models (GPT-4o, GPT-4 Vision, etc.)

1 lines 14.8 kB
{"version":3,"sources":["../src/models.ts","../src/calculator.ts"],"sourcesContent":["/**\n * Model configurations for different OpenAI Vision models\n */\n\nexport interface ModelConfig {\n /**\n * Model identifier\n */\n id: string;\n \n /**\n * Human-readable model name\n */\n name: string;\n \n /**\n * Base tokens for high detail mode\n */\n baseTokens: number;\n \n /**\n * Tokens per tile for high detail mode\n */\n tokensPerTile: number;\n \n /**\n * Size of each tile in pixels\n */\n tileSize: number;\n \n /**\n * Maximum dimension before scaling\n */\n maxDimension: number;\n \n /**\n * Target size for shortest side\n */\n shortSideTarget: number;\n \n /**\n * Tokens for low detail mode\n */\n lowDetailTokens: number;\n \n /**\n * Default cost per 1k input tokens in USD\n */\n defaultCostPer1kTokens: number;\n \n /**\n * Whether this model supports vision\n */\n supportsVision: boolean;\n \n /**\n * Additional notes about the model\n */\n notes?: string;\n}\n\n/**\n * Available models\n */\nexport const MODELS: Record<string, ModelConfig> = {\n 'gpt-4o': {\n id: 'gpt-4o',\n name: 'GPT-4o',\n baseTokens: 85,\n tokensPerTile: 170,\n tileSize: 512,\n maxDimension: 2048,\n shortSideTarget: 768,\n lowDetailTokens: 85,\n defaultCostPer1kTokens: 5.00,\n supportsVision: true,\n notes: 'Latest multimodal model with vision capabilities'\n },\n \n 'gpt-4o-2024-05-13': {\n id: 'gpt-4o-2024-05-13',\n name: 'GPT-4o (2024-05-13)',\n baseTokens: 85,\n tokensPerTile: 170,\n tileSize: 512,\n maxDimension: 2048,\n shortSideTarget: 768,\n lowDetailTokens: 85,\n defaultCostPer1kTokens: 5.00,\n supportsVision: true,\n notes: 'Specific version of GPT-4o'\n },\n \n 'gpt-4o-mini': {\n id: 'gpt-4o-mini',\n name: 'GPT-4o Mini',\n baseTokens: 85,\n tokensPerTile: 170,\n tileSize: 512,\n maxDimension: 2048,\n shortSideTarget: 768,\n lowDetailTokens: 85,\n defaultCostPer1kTokens: 0.15,\n supportsVision: true,\n notes: 'Smaller, more affordable version of GPT-4o'\n },\n \n 'gpt-4-vision-preview': {\n id: 'gpt-4-vision-preview',\n name: 'GPT-4 Vision Preview',\n baseTokens: 85,\n tokensPerTile: 170,\n tileSize: 512,\n maxDimension: 2048,\n shortSideTarget: 768,\n lowDetailTokens: 85,\n defaultCostPer1kTokens: 10.00,\n supportsVision: true,\n notes: 'Original GPT-4 with vision capabilities'\n },\n \n 'gpt-4-turbo': {\n id: 'gpt-4-turbo',\n name: 'GPT-4 Turbo',\n baseTokens: 85,\n tokensPerTile: 170,\n tileSize: 512,\n maxDimension: 2048,\n shortSideTarget: 768,\n lowDetailTokens: 85,\n defaultCostPer1kTokens: 10.00,\n supportsVision: true,\n notes: 'GPT-4 Turbo with vision support'\n },\n \n 'gpt-4-turbo-2024-04-09': {\n id: 'gpt-4-turbo-2024-04-09',\n name: 'GPT-4 Turbo (2024-04-09)',\n baseTokens: 85,\n tokensPerTile: 170,\n tileSize: 512,\n maxDimension: 2048,\n shortSideTarget: 768,\n lowDetailTokens: 85,\n defaultCostPer1kTokens: 10.00,\n supportsVision: true,\n notes: 'Specific version of GPT-4 Turbo with vision'\n },\n \n 'gpt-4-1106-vision-preview': {\n id: 'gpt-4-1106-vision-preview',\n name: 'GPT-4 Vision (1106)',\n baseTokens: 85,\n tokensPerTile: 170,\n tileSize: 512,\n maxDimension: 2048,\n shortSideTarget: 768,\n lowDetailTokens: 85,\n defaultCostPer1kTokens: 10.00,\n supportsVision: true,\n notes: 'November 2023 version of GPT-4 Vision'\n }\n};\n\n/**\n * Get a model configuration by ID\n */\nexport function getModel(modelId: string): ModelConfig | undefined {\n return MODELS[modelId];\n}\n\n/**\n * Get all available model IDs\n */\nexport function getAvailableModels(): string[] {\n return Object.keys(MODELS);\n}\n\n/**\n * Get all vision-enabled models\n */\nexport function getVisionModels(): ModelConfig[] {\n return Object.values(MODELS).filter(model => model.supportsVision);\n}\n\n/**\n * Default model ID\n */\nexport const DEFAULT_MODEL = 'gpt-4o';","import { ImageTokenInput, ImageTokenResult, PricingConfig, BatchCalculationResult } from './types';\nimport { getModel, DEFAULT_MODEL, ModelConfig } from './models';\n\n/**\n * Calculate the number of tokens consumed by an image in OpenAI Vision models\n * Based on OpenAI's documentation: https://platform.openai.com/docs/guides/images-vision\n * \n * @param input - Image dimensions, detail level, and model\n * @param pricing - Optional pricing configuration (overrides model default)\n * @returns Token count and cost calculation\n */\nexport function calculateImageTokens(\n input: ImageTokenInput,\n pricing?: PricingConfig\n): ImageTokenResult {\n const { width, height, detail = 'high', model = DEFAULT_MODEL } = input;\n \n // Validate input\n if (width <= 0 || height <= 0) {\n throw new Error('Image dimensions must be positive numbers');\n }\n \n // Get model configuration\n const modelConfig = getModel(model);\n if (!modelConfig) {\n throw new Error(`Unknown model: ${model}. Available models: ${Object.keys(getModel).join(', ')}`);\n }\n \n if (!modelConfig.supportsVision) {\n throw new Error(`Model ${model} does not support vision`);\n }\n \n // Use provided pricing or model default\n const costPer1kTokens = pricing?.costPer1kTokens ?? modelConfig.defaultCostPer1kTokens;\n \n // For low detail, always return configured low detail tokens\n if (detail === 'low') {\n const tokens = modelConfig.lowDetailTokens;\n const cost = (tokens / 1000) * costPer1kTokens;\n \n return {\n tokens,\n cost,\n model: modelConfig.id,\n modelName: modelConfig.name,\n details: {\n originalSize: { width, height },\n resizedSize: { width: 512, height: 512 },\n tiles: { width: 1, height: 1, total: 1 },\n baseTokens: tokens,\n tokensPerTile: 0,\n detailLevel: 'low'\n }\n };\n }\n \n // For high detail or auto (which defaults to high)\n const { resizedWidth, resizedHeight } = resizeImage(width, height, modelConfig);\n \n // Calculate number of tiles\n const tilesWidth = Math.ceil(resizedWidth / modelConfig.tileSize);\n const tilesHeight = Math.ceil(resizedHeight / modelConfig.tileSize);\n const totalTiles = tilesWidth * tilesHeight;\n \n // Calculate total tokens\n const tokens = modelConfig.baseTokens + (modelConfig.tokensPerTile * totalTiles);\n \n // Calculate cost\n const cost = (tokens / 1000) * costPer1kTokens;\n \n return {\n tokens,\n cost,\n model: modelConfig.id,\n modelName: modelConfig.name,\n details: {\n originalSize: { width, height },\n resizedSize: { width: resizedWidth, height: resizedHeight },\n tiles: {\n width: tilesWidth,\n height: tilesHeight,\n total: totalTiles\n },\n baseTokens: modelConfig.baseTokens,\n tokensPerTile: modelConfig.tokensPerTile,\n detailLevel: detail === 'auto' ? 'high' : detail\n }\n };\n}\n\n/**\n * Resize image dimensions according to model rules\n * \n * @param width - Original width\n * @param height - Original height\n * @param modelConfig - Model configuration\n * @returns Resized dimensions\n */\nfunction resizeImage(\n width: number, \n height: number, \n modelConfig: ModelConfig\n): { resizedWidth: number; resizedHeight: number } {\n let resizedWidth = width;\n let resizedHeight = height;\n \n // Step 1: Scale down to fit within max dimension square if necessary\n if (resizedWidth > modelConfig.maxDimension || resizedHeight > modelConfig.maxDimension) {\n const aspectRatio = resizedWidth / resizedHeight;\n \n if (aspectRatio > 1) {\n // Width is larger\n resizedWidth = modelConfig.maxDimension;\n resizedHeight = Math.floor(modelConfig.maxDimension / aspectRatio);\n } else {\n // Height is larger or square\n resizedHeight = modelConfig.maxDimension;\n resizedWidth = Math.floor(modelConfig.maxDimension * aspectRatio);\n }\n }\n \n // Step 2: Scale so shortest side is target size if both dimensions exceed target\n if (resizedWidth > modelConfig.shortSideTarget && resizedHeight > modelConfig.shortSideTarget) {\n const aspectRatio = resizedWidth / resizedHeight;\n \n if (aspectRatio > 1) {\n // Width is larger, scale height to target\n resizedHeight = modelConfig.shortSideTarget;\n resizedWidth = Math.floor(modelConfig.shortSideTarget * aspectRatio);\n } else {\n // Height is larger or square, scale width to target\n resizedWidth = modelConfig.shortSideTarget;\n resizedHeight = Math.floor(modelConfig.shortSideTarget / aspectRatio);\n }\n }\n \n return { resizedWidth, resizedHeight };\n}\n\n/**\n * Calculate tokens for multiple images\n * \n * @param images - Array of image inputs\n * @param pricing - Optional pricing configuration (overrides model defaults)\n * @returns Array of results and total summary\n */\nexport function calculateBatchImageTokens(\n images: ImageTokenInput[],\n pricing?: PricingConfig\n): BatchCalculationResult {\n const results = images.map(image => calculateImageTokens(image, pricing));\n \n const totalTokens = results.reduce((sum, result) => sum + result.tokens, 0);\n const totalCost = results.reduce((sum, result) => sum + result.cost, 0);\n \n // Calculate breakdown by model\n const byModel: Record<string, { count: number; tokens: number; cost: number }> = {};\n \n results.forEach(result => {\n if (!byModel[result.model]) {\n byModel[result.model] = { count: 0, tokens: 0, cost: 0 };\n }\n byModel[result.model].count++;\n byModel[result.model].tokens += result.tokens;\n byModel[result.model].cost += result.cost;\n });\n \n return {\n results,\n summary: {\n totalImages: images.length,\n totalTokens,\n totalCost,\n byModel\n }\n };\n}\n\n/**\n * Get pricing for a specific model\n */\nexport function getPricing(model: string = DEFAULT_MODEL): PricingConfig {\n const modelConfig = getModel(model);\n if (!modelConfig) {\n throw new Error(`Unknown model: ${model}`);\n }\n return { costPer1kTokens: modelConfig.defaultCostPer1kTokens };\n}\n\n/**\n * Create a custom pricing configuration\n */\nexport function createPricing(costPer1kTokens: number): PricingConfig {\n if (costPer1kTokens <= 0) {\n throw new Error('Cost per 1k tokens must be positive');\n }\n \n return { costPer1kTokens };\n}"],"mappings":"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","names":["MODELS","getModel","modelId","getAvailableModels","getVisionModels","model","DEFAULT_MODEL","calculateImageTokens","input","pricing","width","height","detail","model","DEFAULT_MODEL","modelConfig","getModel","costPer1kTokens","tokens","cost","resizedWidth","resizedHeight","resizeImage","tilesWidth","tilesHeight","totalTiles","aspectRatio","calculateBatchImageTokens","images","results","image","totalTokens","sum","result","totalCost","byModel","getPricing","createPricing"]}