array( 'gpt-4.1' => array( 'input' => 0.002, 'output' => 0.008, ), 'gpt-4o' => array( 'input' => 0.0025, 'output' => 0.01, ), 'gpt-4o-mini' => array( 'input' => 0.00015, 'output' => 0.0006, ), 'gpt-3.5-turbo' => array( 'input' => 0.0005, 'output' => 0.0015, ), ), 'anthropic' => array( 'claude-sonnet-4-6' => array( 'input' => 0.003, 'output' => 0.015, ), 'claude-opus-4-6' => array( 'input' => 0.015, 'output' => 0.075, ), 'claude-haiku-4-5-20251001' => array( 'input' => 0.00025, 'output' => 0.00125, ), ), 'gemini' => array( 'gemini-2.0-flash' => array( 'input' => 0.00010, 'output' => 0.00040, ), 'gemini-2.0-flash-lite' => array( 'input' => 0.000038, 'output' => 0.00015, ), 'gemini-1.5-pro' => array( 'input' => 0.00125, 'output' => 0.005, ), ), 'grok' => array( 'grok-3' => array( 'input' => 0.003, 'output' => 0.015, ), 'grok-3-mini' => array( 'input' => 0.0003, 'output' => 0.0005, ), ), ); /** Estimate token count (~4 chars per token) */ public static function estimate( string $text ): int { return (int) ceil( mb_strlen( $text ) / 4 ); } /** Truncate text to approximately $max_tokens */ public static function truncate( string $text, int $max_tokens ): string { $max_chars = $max_tokens * 4; if ( mb_strlen( $text ) <= $max_chars ) { return $text; } return mb_substr( $text, 0, $max_chars ); } /** * Estimate cost in USD. * * @param int $tokens Number of tokens * @param string $provider Provider ID * @param string $model Model ID * @param string $type 'input' or 'output' */ public static function estimateCost( int $tokens, string $provider, string $model, string $type = 'input' ): float { $price_per_1k = self::PRICING[ $provider ][ $model ][ $type ] ?? 0.002; return round( ( $tokens / 1000 ) * $price_per_1k, 6 ); } /** Human-readable cost string e.g. "~0,05 €" */ public static function formatCost( float $usd ): string { $eur = $usd * 0.92; if ( $eur < 0.01 ) { return '< 0,01 €'; } return '~' . number_format( $eur, 2, ',', '.' ) . ' €'; } }