Skip to content
This repository was archived by the owner on Jul 4, 2025. It is now read-only.

Commit 00af979

Browse files
feat: cortex pull and cortex engines install CLI uses API server (#1550)
* fix: add ws and indicators * fix: more * fix: pull models info from server * fix: model_source * fix: rename * fix: download cortexso * fix: pull models * fix: remove comments * fix: rename * fix: change download UI * fix: comment out * fix: e2e tests * fix: run, start * fix: start server * fix: e2e * fix: remove * fix: abort model * fix: build * fix: clean code * fix: clean more * fix: normalize engine id * fix: use auto * fix: use vcpkg for indicators * fix: download progress --------- Co-authored-by: vansangpfiev <sang@jan.ai>
1 parent 04c5c40 commit 00af979

32 files changed

+1526
-81
lines changed

engine/cli/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ find_package(tabulate CONFIG REQUIRED)
7171
find_package(CURL REQUIRED)
7272
find_package(SQLiteCpp REQUIRED)
7373
find_package(Trantor CONFIG REQUIRED)
74+
find_package(indicators CONFIG REQUIRED)
75+
7476

7577
add_executable(${TARGET_NAME} main.cc
7678
${CMAKE_CURRENT_SOURCE_DIR}/../utils/cpuid/cpu_info.cc
@@ -80,6 +82,8 @@ add_executable(${TARGET_NAME} main.cc
8082
${CMAKE_CURRENT_SOURCE_DIR}/../services/engine_service.cc
8183
${CMAKE_CURRENT_SOURCE_DIR}/../services/model_service.cc
8284
${CMAKE_CURRENT_SOURCE_DIR}/../services/inference_service.cc
85+
${CMAKE_CURRENT_SOURCE_DIR}/utils/easywsclient.cc
86+
${CMAKE_CURRENT_SOURCE_DIR}/utils/download_progress.cc
8387
)
8488

8589
target_link_libraries(${TARGET_NAME} PRIVATE httplib::httplib)
@@ -93,6 +97,7 @@ target_link_libraries(${TARGET_NAME} PRIVATE JsonCpp::JsonCpp OpenSSL::SSL OpenS
9397
${CMAKE_THREAD_LIBS_INIT})
9498
target_link_libraries(${TARGET_NAME} PRIVATE SQLiteCpp)
9599
target_link_libraries(${TARGET_NAME} PRIVATE Trantor::Trantor)
100+
target_link_libraries(${TARGET_NAME} PRIVATE indicators::indicators)
96101

97102
# ##############################################################################
98103

engine/cli/command_line_parser.cc

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,9 @@ void CommandLineParser::SetupCommonCommands() {
130130
return;
131131
}
132132
try {
133-
commands::ModelPullCmd(download_service_).Exec(cml_data_.model_id);
133+
commands::ModelPullCmd(download_service_)
134+
.Exec(cml_data_.config.apiServerHost,
135+
std::stoi(cml_data_.config.apiServerPort), cml_data_.model_id);
134136
} catch (const std::exception& e) {
135137
CLI_LOG(e.what());
136138
}
@@ -462,7 +464,9 @@ void CommandLineParser::EngineInstall(CLI::App* parent,
462464
if (std::exchange(executed_, true))
463465
return;
464466
try {
465-
commands::EngineInstallCmd(download_service_)
467+
commands::EngineInstallCmd(download_service_,
468+
cml_data_.config.apiServerHost,
469+
std::stoi(cml_data_.config.apiServerPort))
466470
.Exec(engine_name, version, src);
467471
} catch (const std::exception& e) {
468472
CTL_ERR(e.what());
Lines changed: 59 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,68 @@
11
#include "engine_install_cmd.h"
2+
#include "server_start_cmd.h"
3+
#include "utils/download_progress.h"
4+
#include "utils/engine_constants.h"
5+
#include "utils/json_helper.h"
26
#include "utils/logging_utils.h"
37

48
namespace commands {
5-
6-
void EngineInstallCmd::Exec(const std::string& engine,
9+
bool EngineInstallCmd::Exec(const std::string& engine,
710
const std::string& version,
811
const std::string& src) {
9-
auto result = engine_service_.InstallEngine(engine, version, src);
10-
if (result.has_error()) {
11-
CLI_LOG(result.error());
12-
} else if(result && result.value()){
13-
CLI_LOG("Engine " << engine << " installed successfully!");
12+
// Handle local install, if fails, fallback to remote install
13+
if (!src.empty()) {
14+
auto res = engine_service_.UnzipEngine(engine, version, src);
15+
if (res.has_error()) {
16+
CLI_LOG(res.error());
17+
return false;
18+
}
19+
if (res.value()) {
20+
CLI_LOG("Engine " << engine << " installed successfully!");
21+
return true;
22+
}
23+
}
24+
25+
// Start server if server is not started yet
26+
if (!commands::IsServerAlive(host_, port_)) {
27+
CLI_LOG("Starting server ...");
28+
commands::ServerStartCmd ssc;
29+
if (!ssc.Exec(host_, port_)) {
30+
return false;
31+
}
32+
}
33+
34+
httplib::Client cli(host_ + ":" + std::to_string(port_));
35+
Json::Value json_data;
36+
auto data_str = json_data.toStyledString();
37+
cli.set_read_timeout(std::chrono::seconds(60));
38+
auto res = cli.Post("/v1/engines/install/" + engine, httplib::Headers(),
39+
data_str.data(), data_str.size(), "application/json");
40+
41+
if (res) {
42+
if (res->status != httplib::StatusCode::OK_200) {
43+
auto root = json_helper::ParseJsonString(res->body);
44+
CLI_LOG(root["message"].asString());
45+
return false;
46+
}
47+
} else {
48+
auto err = res.error();
49+
CTL_ERR("HTTP error: " << httplib::to_string(err));
50+
return false;
1451
}
52+
53+
CLI_LOG("Start downloading ...")
54+
DownloadProgress dp;
55+
dp.Connect(host_, port_);
56+
if (!dp.Handle(engine))
57+
return false;
58+
59+
bool check_cuda_download = !system_info_utils::GetCudaVersion().empty();
60+
if (check_cuda_download) {
61+
if (!dp.Handle("cuda"))
62+
return false;
63+
}
64+
65+
CLI_LOG("Engine " << engine << " downloaded successfully!")
66+
return true;
1567
}
1668
}; // namespace commands

engine/cli/commands/engine_install_cmd.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@ namespace commands {
77

88
class EngineInstallCmd {
99
public:
10-
explicit EngineInstallCmd(std::shared_ptr<DownloadService> download_service)
11-
: engine_service_{EngineService(download_service)} {};
10+
explicit EngineInstallCmd(std::shared_ptr<DownloadService> download_service, const std::string& host, int port)
11+
: engine_service_{EngineService(download_service)}, host_(host), port_(port) {};
1212

13-
void Exec(const std::string& engine, const std::string& version = "latest",
13+
bool Exec(const std::string& engine, const std::string& version = "latest",
1414
const std::string& src = "");
1515

1616
private:
1717
EngineService engine_service_;
18+
std::string host_;
19+
int port_;
1820
};
1921
} // namespace commands
Lines changed: 174 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,181 @@
11
#include "model_pull_cmd.h"
2+
#include <memory>
3+
#include "common/event.h"
4+
#include "database/models.h"
5+
#include "server_start_cmd.h"
6+
#include "utils/cli_selection_utils.h"
7+
#include "utils/download_progress.h"
8+
#include "utils/format_utils.h"
9+
#include "utils/huggingface_utils.h"
10+
#include "utils/json_helper.h"
211
#include "utils/logging_utils.h"
12+
#include "utils/scope_exit.h"
13+
#include "utils/string_utils.h"
14+
#if defined(_WIN32)
15+
#include <signal.h>
16+
#endif
317

418
namespace commands {
5-
void ModelPullCmd::Exec(const std::string& input) {
6-
auto result = model_service_.DownloadModel(input);
7-
if (result.has_error()) {
8-
CLI_LOG(result.error());
19+
std::function<void(int)> shutdown_handler;
20+
inline void signal_handler(int signal) {
21+
if (shutdown_handler) {
22+
shutdown_handler(signal);
23+
}
24+
}
25+
std::optional<std::string> ModelPullCmd::Exec(const std::string& host, int port,
26+
const std::string& input) {
27+
28+
// model_id: use to check the download progress
29+
// model: use as a parameter for pull API
30+
auto model_id = input;
31+
auto model = input;
32+
33+
// Start server if server is not started yet
34+
if (!commands::IsServerAlive(host, port)) {
35+
CLI_LOG("Starting server ...");
36+
commands::ServerStartCmd ssc;
37+
if (!ssc.Exec(host, port)) {
38+
return std::nullopt;
39+
}
40+
}
41+
42+
// Get model info from Server
43+
httplib::Client cli(host + ":" + std::to_string(port));
44+
cli.set_read_timeout(std::chrono::seconds(60));
45+
Json::Value j_data;
46+
j_data["model"] = input;
47+
auto d_str = j_data.toStyledString();
48+
auto res = cli.Post("/models/pull/info", httplib::Headers(), d_str.data(),
49+
d_str.size(), "application/json");
50+
51+
if (res) {
52+
if (res->status == httplib::StatusCode::OK_200) {
53+
// CLI_LOG(res->body);
54+
auto root = json_helper::ParseJsonString(res->body);
55+
auto id = root["id"].asString();
56+
bool is_cortexso = root["modelSource"].asString() == "cortexso";
57+
auto default_branch = root["defaultBranch"].asString();
58+
std::vector<std::string> downloaded;
59+
for (auto const& v : root["downloadedModels"]) {
60+
downloaded.push_back(v.asString());
61+
}
62+
std::vector<std::string> avails;
63+
for (auto const& v : root["availableModels"]) {
64+
avails.push_back(v.asString());
65+
}
66+
auto download_url = root["downloadUrl"].asString();
67+
68+
if (downloaded.empty() && avails.empty()) {
69+
model_id = id;
70+
model = download_url;
71+
} else {
72+
if (is_cortexso) {
73+
auto selection = cli_selection_utils::PrintModelSelection(
74+
downloaded, avails,
75+
default_branch.empty()
76+
? std::nullopt
77+
: std::optional<std::string>(default_branch));
78+
79+
if (!selection.has_value()) {
80+
CLI_LOG("Invalid selection");
81+
return std::nullopt;
82+
}
83+
model_id = selection.value();
84+
model = model_id;
85+
} else {
86+
auto selection = cli_selection_utils::PrintSelection(avails);
87+
CLI_LOG("Selected: " << selection.value());
88+
model_id = id + ":" + selection.value();
89+
model = download_url + selection.value();
90+
}
91+
}
92+
} else {
93+
auto root = json_helper::ParseJsonString(res->body);
94+
CLI_LOG(root["message"].asString());
95+
return std::nullopt;
96+
}
97+
} else {
98+
auto err = res.error();
99+
CTL_ERR("HTTP error: " << httplib::to_string(err));
100+
return std::nullopt;
101+
}
102+
103+
// Send request download model to server
104+
Json::Value json_data;
105+
json_data["model"] = model;
106+
auto data_str = json_data.toStyledString();
107+
cli.set_read_timeout(std::chrono::seconds(60));
108+
res = cli.Post("/v1/models/pull", httplib::Headers(), data_str.data(),
109+
data_str.size(), "application/json");
110+
111+
if (res) {
112+
if (res->status != httplib::StatusCode::OK_200) {
113+
auto root = json_helper::ParseJsonString(res->body);
114+
CLI_LOG(root["message"].asString());
115+
return std::nullopt;
116+
}
117+
} else {
118+
auto err = res.error();
119+
CTL_ERR("HTTP error: " << httplib::to_string(err));
120+
return std::nullopt;
121+
}
122+
123+
CLI_LOG("Start downloading ...")
124+
DownloadProgress dp;
125+
bool force_stop = false;
126+
127+
shutdown_handler = [this, &dp, &host, &port, &model_id, &force_stop](int) {
128+
force_stop = true;
129+
AbortModelPull(host, port, model_id);
130+
dp.ForceStop();
131+
};
132+
133+
utils::ScopeExit se([]() { shutdown_handler = {}; });
134+
#if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__))
135+
struct sigaction sigint_action;
136+
sigint_action.sa_handler = signal_handler;
137+
sigemptyset(&sigint_action.sa_mask);
138+
sigint_action.sa_flags = 0;
139+
sigaction(SIGINT, &sigint_action, NULL);
140+
sigaction(SIGTERM, &sigint_action, NULL);
141+
#elif defined(_WIN32)
142+
auto console_ctrl_handler = +[](DWORD ctrl_type) -> BOOL {
143+
return (ctrl_type == CTRL_C_EVENT) ? (signal_handler(SIGINT), true) : false;
144+
};
145+
SetConsoleCtrlHandler(
146+
reinterpret_cast<PHANDLER_ROUTINE>(console_ctrl_handler), true);
147+
#endif
148+
dp.Connect(host, port);
149+
if (!dp.Handle(model_id))
150+
return std::nullopt;
151+
if (force_stop)
152+
return std::nullopt;
153+
CLI_LOG("Model " << model_id << " downloaded successfully!")
154+
return model_id;
155+
}
156+
157+
bool ModelPullCmd::AbortModelPull(const std::string& host, int port,
158+
const std::string& task_id) {
159+
Json::Value json_data;
160+
json_data["taskId"] = task_id;
161+
auto data_str = json_data.toStyledString();
162+
httplib::Client cli(host + ":" + std::to_string(port));
163+
cli.set_read_timeout(std::chrono::seconds(60));
164+
auto res = cli.Delete("/v1/models/pull", httplib::Headers(), data_str.data(),
165+
data_str.size(), "application/json");
166+
if (res) {
167+
if (res->status == httplib::StatusCode::OK_200) {
168+
CTL_INF("Abort model pull successfully: " << task_id);
169+
return true;
170+
} else {
171+
auto root = json_helper::ParseJsonString(res->body);
172+
CLI_LOG(root["message"].asString());
173+
return false;
174+
}
175+
} else {
176+
auto err = res.error();
177+
CTL_ERR("HTTP error: " << httplib::to_string(err));
178+
return false;
9179
}
10180
}
11181
}; // namespace commands

engine/cli/commands/model_pull_cmd.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,14 @@ class ModelPullCmd {
88
public:
99
explicit ModelPullCmd(std::shared_ptr<DownloadService> download_service)
1010
: model_service_{ModelService(download_service)} {};
11-
void Exec(const std::string& input);
11+
explicit ModelPullCmd(const ModelService& model_service)
12+
: model_service_{model_service} {};
13+
std::optional<std::string> Exec(const std::string& host, int port,
14+
const std::string& input);
15+
16+
private:
17+
bool AbortModelPull(const std::string& host, int port,
18+
const std::string& task_id);
1219

1320
private:
1421
ModelService model_service_;

engine/cli/commands/model_start_cmd.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ bool ModelStartCmd::Exec(const std::string& host, int port,
1414
const std::string& model_handle,
1515
bool print_success_log) {
1616
std::optional<std::string> model_id =
17-
SelectLocalModel(model_service_, model_handle);
17+
SelectLocalModel(host, port, model_service_, model_handle);
1818

1919
if (!model_id.has_value()) {
2020
return false;

0 commit comments

Comments
 (0)