@@ -50,6 +50,22 @@ using json = nlohmann::ordered_json;
5050// downloader
5151//
5252
53+ // validate repo name format: owner/repo
54+ static bool validate_repo_name (const std::string & repo) {
55+ static const std::regex repo_regex (R"( ^[A-Za-z0-9_.\-]+\/[A-Za-z0-9_.\-]+$)" );
56+ return std::regex_match (repo, repo_regex);
57+ }
58+
59+ static std::string get_manifest_path (const std::string & repo, const std::string & tag) {
60+ // we use "=" to avoid clashing with other component, while still being allowed on windows
61+ std::string fname = " manifest=" + repo + " =" + tag + " .json" ;
62+ if (!validate_repo_name (repo)) {
63+ throw std::runtime_error (" error: repo name must be in the format 'owner/repo'" );
64+ }
65+ string_replace_all (fname, " /" , " =" );
66+ return fs_get_cache_file (fname);
67+ }
68+
5369static std::string read_file (const std::string & fname) {
5470 std::ifstream file (fname);
5571 if (!file) {
@@ -829,17 +845,13 @@ common_hf_file_res common_get_hf_file(const std::string & hf_repo_with_tag, cons
829845 // Important: the User-Agent must be "llama-cpp" to get the "ggufFile" field in the response
830846 // User-Agent header is already set in common_remote_get_content, no need to set it here
831847
832- // we use "=" to avoid clashing with other component, while still being allowed on windows
833- std::string cached_response_fname = " manifest=" + hf_repo + " =" + tag + " .json" ;
834- string_replace_all (cached_response_fname, " /" , " _" );
835- std::string cached_response_path = fs_get_cache_file (cached_response_fname);
836-
837848 // make the request
838849 common_remote_params params;
839850 params.headers = headers;
840851 long res_code = 0 ;
841852 std::string res_str;
842853 bool use_cache = false ;
854+ std::string cached_response_path = get_manifest_path (hf_repo, tag);
843855 if (!offline) {
844856 try {
845857 auto res = common_remote_get_content (url, params);
@@ -895,6 +907,33 @@ common_hf_file_res common_get_hf_file(const std::string & hf_repo_with_tag, cons
895907 return { hf_repo, ggufFile, mmprojFile };
896908}
897909
910+ std::vector<common_cached_model_info> common_list_cached_models () {
911+ std::vector<common_cached_model_info> models;
912+ const std::string cache_dir = fs_get_cache_directory ();
913+ const std::vector<common_file_info> files = fs_list_files (cache_dir);
914+ for (const auto & file : files) {
915+ if (string_starts_with (file.name , " manifest=" ) && string_ends_with (file.name , " .json" )) {
916+ common_cached_model_info model_info;
917+ model_info.manifest_path = file.path ;
918+ std::string fname = file.name ;
919+ string_replace_all (fname, " .json" , " " ); // remove extension
920+ auto parts = string_split<std::string>(fname, ' =' );
921+ if (parts.size () == 4 ) {
922+ // expect format: manifest=<user>=<model>=<tag>=<other>
923+ model_info.user = parts[1 ];
924+ model_info.model = parts[2 ];
925+ model_info.tag = parts[3 ];
926+ } else {
927+ // invalid format
928+ continue ;
929+ }
930+ model_info.size = 0 ; // TODO: get GGUF size, not manifest size
931+ models.push_back (model_info);
932+ }
933+ }
934+ return models;
935+ }
936+
898937//
899938// Docker registry functions
900939//
@@ -959,6 +998,7 @@ std::string common_docker_resolve_model(const std::string & docker) {
959998 std::string token = common_docker_get_token (repo); // Get authentication token
960999
9611000 // Get manifest
1001+ // TODO: cache the manifest response so that it appears in the model list
9621002 const std::string url_prefix = " https://registry-1.docker.io/v2/" + repo;
9631003 std::string manifest_url = url_prefix + " /manifests/" + tag;
9641004 common_remote_params manifest_params;
0 commit comments