@@ -244,6 +244,10 @@ static bool curl_perform_with_retry(const std::string & url, CURL * curl, int ma
244244    return  false ;
245245}
246246
247+ struct  FILE_deleter  {
248+     void  operator ()(FILE * f) const  { fclose (f); }
249+ };
250+ 
247251//  download one single file from remote URL to local path
248252static  bool  common_download_file_single (const  std::string & url, const  std::string & path, const  std::string & bearer_token, bool  offline) {
249253    //  Check if the file already exists locally
@@ -389,13 +393,6 @@ static bool common_download_file_single(const std::string & url, const std::stri
389393        }
390394
391395        //  Set the output file
392- 
393-         struct  FILE_deleter  {
394-             void  operator ()(FILE * f) const  {
395-                 fclose (f);
396-             }
397-         };
398- 
399396        std::unique_ptr<FILE, FILE_deleter> outfile (fopen (path_temporary.c_str (), " wb"  ));
400397        if  (!outfile) {
401398            LOG_ERR (" %s: error opening local file for writing: %s\n "  , __func__, path.c_str ());
@@ -745,6 +742,192 @@ std::pair<long, std::vector<char>> common_remote_get_content(const std::string &
745742
746743#endif  //  LLAMA_USE_CURL
747744
745+ // 
746+ //  Docker registry functions
747+ // 
748+ 
749+ static  std::string common_docker_get_token (const  std::string & repo) {
750+     std::string url = " https://auth.docker.io/token?service=registry.docker.io&scope=repository:"   + repo + " :pull"  ;
751+ 
752+     common_remote_params params;
753+     auto                  res = common_remote_get_content (url, params);
754+ 
755+     if  (res.first  != 200 ) {
756+         throw  std::runtime_error (" Failed to get Docker registry token, HTTP code: "   + std::to_string (res.first ));
757+     }
758+ 
759+     std::string            response_str (res.second .begin (), res.second .end ());
760+     nlohmann::ordered_json response = nlohmann::ordered_json::parse (response_str);
761+ 
762+     if  (!response.contains (" token"  )) {
763+         throw  std::runtime_error (" Docker registry token response missing 'token' field"  );
764+     }
765+ 
766+     return  response[" token"  ].get <std::string>();
767+ }
768+ 
769+ #ifdef  LLAMA_USE_CURL
770+ 
771+ //  Helper function to download Docker blob directly to file
772+ static  bool  common_docker_download_blob (const  std::string & blob_url,
773+                                         const  std::string & token,
774+                                         const  std::string & local_path) {
775+     curl_ptr       curl (curl_easy_init (), &curl_easy_cleanup);
776+     curl_slist_ptr http_headers;
777+     if  (!curl.get ()) {
778+         LOG_ERR (" %s: curl_easy_init() failed\n "  , __func__);
779+         return  false ;
780+     }
781+ 
782+     //  Prepare temporary filename for safe downloading
783+     std::string path_temporary = local_path + " .tmp"  ;
784+     std::unique_ptr<FILE, FILE_deleter> outfile (fopen (path_temporary.c_str (), " wb"  ));
785+     if  (!outfile) {
786+         LOG_ERR (" %s: error opening local file for writing: %s\n "  , __func__, path_temporary.c_str ());
787+         return  false ;
788+     }
789+ 
790+     //  Set up CURL options
791+     curl_easy_setopt (curl.get (), CURLOPT_URL, blob_url.c_str ());
792+     curl_easy_setopt (curl.get (), CURLOPT_NOPROGRESS, 1L );
793+     curl_easy_setopt (curl.get (), CURLOPT_FOLLOWLOCATION, 1L );
794+ 
795+     //  Set up write callback to stream directly to file
796+     typedef  size_t  (*CURLOPT_WRITEFUNCTION_PTR)(void  * data, size_t  size, size_t  nmemb, void  * fd);
797+     auto  write_callback = [](void  * data, size_t  size, size_t  nmemb, void  * fd) -> size_t  {
798+         return  fwrite (data, size, nmemb, (FILE *) fd);
799+     };
800+     curl_easy_setopt (curl.get (), CURLOPT_WRITEFUNCTION, static_cast <CURLOPT_WRITEFUNCTION_PTR>(write_callback));
801+     curl_easy_setopt (curl.get (), CURLOPT_WRITEDATA, outfile.get ());
802+ 
803+ #    if  defined(_WIN32)
804+     curl_easy_setopt (curl.get (), CURLOPT_SSL_OPTIONS, CURLSSLOPT_NATIVE_CA);
805+ #    endif 
806+ 
807+     //  Set headers
808+     http_headers.ptr         = curl_slist_append (http_headers.ptr , " User-Agent: llama-cpp"  );
809+     std::string auth_header = " Authorization: Bearer "   + token;
810+     http_headers.ptr         = curl_slist_append (http_headers.ptr , auth_header.c_str ());
811+     curl_easy_setopt (curl.get (), CURLOPT_HTTPHEADER, http_headers.ptr );
812+ 
813+     //  Perform the download
814+     CURLcode res = curl_easy_perform (curl.get ());
815+     if  (res != CURLE_OK) {
816+         LOG_ERR (" %s: curl_easy_perform() failed: %s\n "  , __func__, curl_easy_strerror (res));
817+         outfile.reset ();  //  Close file before removing
818+         std::filesystem::remove (path_temporary);
819+         return  false ;
820+     }
821+ 
822+     //  Check HTTP response code
823+     long  response_code;
824+     curl_easy_getinfo (curl.get (), CURLINFO_RESPONSE_CODE, &response_code);
825+     if  (response_code != 200 ) {
826+         LOG_ERR (" %s: HTTP error %ld\n "  , __func__, response_code);
827+         outfile.reset ();  //  Close file before removing
828+         std::filesystem::remove (path_temporary);
829+         return  false ;
830+     }
831+ 
832+     //  Close the file and move to final location
833+     outfile.reset ();
834+ 
835+     if  (std::filesystem::exists (local_path)) {
836+         std::filesystem::remove (local_path);
837+     }
838+ 
839+     std::filesystem::rename (path_temporary, local_path);
840+ 
841+     return  true ;
842+ }
843+ 
844+ #else 
845+ 
846+ static  bool  common_docker_download_blob (const  std::string &, const  std::string &, const  std::string &) {
847+     LOG_ERR (" error: built without CURL, cannot download Docker blob\n "  );
848+     return  false ;
849+ }
850+ 
851+ #endif   //  LLAMA_USE_CURL
852+ 
853+ std::string common_docker_resolve_model (const  std::string & docker_url) {
854+     //  Parse docker://ai/smollm2:135M-Q4_K_M
855+     if  (docker_url.substr (0 , 9 ) != " docker://"  ) {
856+         return  docker_url;  //  Not a docker URL, return as-is
857+     }
858+ 
859+     std::string model_spec = docker_url.substr (9 );  //  Remove "docker://"
860+ 
861+     //  Parse ai/smollm2:135M-Q4_K_M
862+     size_t       colon_pos = model_spec.find (' :'  );
863+     std::string repo, tag;
864+     if  (colon_pos != std::string::npos) {
865+         repo = model_spec.substr (0 , colon_pos);
866+         tag  = model_spec.substr (colon_pos + 1 );
867+     } else  {
868+         repo = model_spec;
869+         tag  = " latest"  ;
870+     }
871+ 
872+     LOG_INF (" Downloading Docker AI model: %s:%s\n "  , repo.c_str (), tag.c_str ());
873+     try  {
874+         std::string token = common_docker_get_token (repo);  //  Get authentication token
875+ 
876+         //  Get manifest
877+         std::string          manifest_url = " https://registry-1.docker.io/v2/"   + repo + " /manifests/"   + tag;
878+         common_remote_params manifest_params;
879+         manifest_params.headers .push_back (" Authorization: Bearer "   + token);
880+         manifest_params.headers .push_back (
881+             " Accept: application/vnd.docker.distribution.manifest.v2+json,application/vnd.oci.image.manifest.v1+json"  );
882+         auto  manifest_res = common_remote_get_content (manifest_url, manifest_params);
883+         if  (manifest_res.first  != 200 ) {
884+             throw  std::runtime_error (" Failed to get Docker manifest, HTTP code: "   + std::to_string (manifest_res.first ));
885+         }
886+ 
887+         std::string            manifest_str (manifest_res.second .begin (), manifest_res.second .end ());
888+         nlohmann::ordered_json manifest = nlohmann::ordered_json::parse (manifest_str);
889+         std::string            gguf_digest;  //  Find the GGUF layer
890+         if  (manifest.contains (" layers"  )) {
891+             for  (const  auto  & layer : manifest[" layers"  ]) {
892+                 if  (layer.contains (" mediaType"  )) {
893+                     std::string media_type = layer[" mediaType"  ].get <std::string>();
894+                     if  (media_type == " application/vnd.docker.ai.gguf.v3"   ||
895+                         media_type.find (" gguf"  ) != std::string::npos) {
896+                         gguf_digest = layer[" digest"  ].get <std::string>();
897+                         break ;
898+                     }
899+                 }
900+             }
901+         }
902+ 
903+         if  (gguf_digest.empty ()) {
904+             throw  std::runtime_error (" No GGUF layer found in Docker manifest"  );
905+         }
906+ 
907+         //  Prepare local filename
908+         std::string model_filename = repo;
909+         std::replace (model_filename.begin (), model_filename.end (), ' /'  , ' _'  );
910+         model_filename += " _"   + tag + " .gguf"  ;
911+         std::string local_path = fs_get_cache_file (model_filename);
912+         if  (std::filesystem::exists (local_path)) {  //  Check if already downloaded
913+             LOG_INF (" Docker model already cached: %s\n "  , local_path.c_str ());
914+             return  local_path;
915+         }
916+ 
917+         //  Download the blob using streaming approach
918+         std::string blob_url = " https://registry-1.docker.io/v2/"   + repo + " /blobs/"   + gguf_digest;
919+         if  (!common_docker_download_blob (blob_url, token, local_path)) {
920+             throw  std::runtime_error (" Failed to download Docker blob"  );
921+         }
922+ 
923+         LOG_INF (" Downloaded Docker model to: %s\n "  , local_path.c_str ());
924+         return  local_path;
925+     } catch  (const  std::exception & e) {
926+         LOG_ERR (" Docker model download failed: %s\n "  , e.what ());
927+         throw ;
928+     }
929+ }
930+ 
748931// 
749932//  utils
750933// 
0 commit comments