@@ -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) {
@@ -851,17 +867,13 @@ common_hf_file_res common_get_hf_file(const std::string & hf_repo_with_tag, cons
851867 // Important: the User-Agent must be "llama-cpp" to get the "ggufFile" field in the response
852868 // User-Agent header is already set in common_remote_get_content, no need to set it here
853869
854- // we use "=" to avoid clashing with other component, while still being allowed on windows
855- std::string cached_response_fname = " manifest=" + hf_repo + " =" + tag + " .json" ;
856- string_replace_all (cached_response_fname, " /" , " _" );
857- std::string cached_response_path = fs_get_cache_file (cached_response_fname);
858-
859870 // make the request
860871 common_remote_params params;
861872 params.headers = headers;
862873 long res_code = 0 ;
863874 std::string res_str;
864875 bool use_cache = false ;
876+ std::string cached_response_path = get_manifest_path (hf_repo, tag);
865877 if (!offline) {
866878 try {
867879 auto res = common_remote_get_content (url, params);
@@ -917,6 +929,33 @@ common_hf_file_res common_get_hf_file(const std::string & hf_repo_with_tag, cons
917929 return { hf_repo, ggufFile, mmprojFile };
918930}
919931
932+ std::vector<common_cached_model_info> common_list_cached_models () {
933+ std::vector<common_cached_model_info> models;
934+ const std::string cache_dir = fs_get_cache_directory ();
935+ const std::vector<common_file_info> files = fs_list_files (cache_dir);
936+ for (const auto & file : files) {
937+ if (string_starts_with (file.name , " manifest=" ) && string_ends_with (file.name , " .json" )) {
938+ common_cached_model_info model_info;
939+ model_info.manifest_path = file.path ;
940+ std::string fname = file.name ;
941+ string_replace_all (fname, " .json" , " " ); // remove extension
942+ auto parts = string_split<std::string>(fname, ' =' );
943+ if (parts.size () == 4 ) {
944+ // expect format: manifest=<user>=<model>=<tag>=<other>
945+ model_info.user = parts[1 ];
946+ model_info.model = parts[2 ];
947+ model_info.tag = parts[3 ];
948+ } else {
949+ // invalid format
950+ continue ;
951+ }
952+ model_info.size = 0 ; // TODO: get GGUF size, not manifest size
953+ models.push_back (model_info);
954+ }
955+ }
956+ return models;
957+ }
958+
920959//
921960// Docker registry functions
922961//
@@ -981,6 +1020,7 @@ std::string common_docker_resolve_model(const std::string & docker) {
9811020 std::string token = common_docker_get_token (repo); // Get authentication token
9821021
9831022 // Get manifest
1023+ // TODO: cache the manifest response so that it appears in the model list
9841024 const std::string url_prefix = " https://registry-1.docker.io/v2/" + repo;
9851025 std::string manifest_url = url_prefix + " /manifests/" + tag;
9861026 common_remote_params manifest_params;
0 commit comments