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

Commit 3838a36

Browse files
Feat: init environment interface
1 parent 8bdca01 commit 3838a36

File tree

7 files changed

+245
-14
lines changed

7 files changed

+245
-14
lines changed

engine/common/download_task.h

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,14 @@
66
#include <sstream>
77
#include <string>
88

9-
enum class DownloadType { Model, Engine, Miscellaneous, CudaToolkit, Cortex };
9+
enum class DownloadType {
10+
Model,
11+
Engine,
12+
Miscellaneous,
13+
CudaToolkit,
14+
Cortex,
15+
Environments
16+
};
1017

1118
struct DownloadItem {
1219

@@ -48,6 +55,8 @@ inline std::string DownloadTypeToString(DownloadType type) {
4855
return "CudaToolkit";
4956
case DownloadType::Cortex:
5057
return "Cortex";
58+
case DownloadType::Environments:
59+
return "Environments";
5160
default:
5261
return "Unknown";
5362
}
@@ -64,6 +73,8 @@ inline DownloadType DownloadTypeFromString(const std::string& str) {
6473
return DownloadType::CudaToolkit;
6574
} else if (str == "Cortex") {
6675
return DownloadType::Cortex;
76+
} else if (str == "Environments") {
77+
return DownloadType::Environments;
6778
} else {
6879
return DownloadType::Miscellaneous;
6980
}

engine/extensions/python-engine/python_engine.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,7 @@ void PythonEngine::GetModels(
319319
void PythonEngine::LoadModel(
320320
std::shared_ptr<Json::Value> json_body,
321321
std::function<void(Json::Value&&, Json::Value&&)>&& callback) {
322+
// TODO: handle a case that can spawn process but the process spawn fail.
322323
pid_t pid;
323324
if (!json_body->isMember("model") || !json_body->isMember("model_path")) {
324325
Json::Value error;

engine/services/environment_serrvice.cc

Whitespace-only changes.
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
#pragma once
2+
#include <filesystem>
3+
#include <memory>
4+
#include <mutex>
5+
#include <optional>
6+
#include <string>
7+
#include <string_view>
8+
#include <unordered_map>
9+
#include <vector>
10+
#include "utils/environment_utils.h"
11+
#include "utils/system_info_utils.h"
12+
13+
using Environment = environment_utils::Environment;
14+
15+
struct EnvironmentsUpdateResult {
16+
Environment environment;
17+
std::string from;
18+
std::string to;
19+
20+
Json::Value ToJson() const {
21+
Json::Value root;
22+
root["environment"] = environment.ToJson();
23+
root["from"] = from;
24+
root["to"] = to;
25+
return root;
26+
}
27+
};
28+
29+
class EnvironmentService {
30+
public:
31+
cpp::result<bool, std::string> IsEnvironmentReady(
32+
const std::string& environment);
33+
cpp::result<void, std::string> InstallEnvironmentAsync(
34+
const std::string& environment, const std::string& version);
35+
cpp::result<void, std::string> UnInstallEnvironment(
36+
const std::string& environment, const std::string& version);
37+
cpp::result<std::vector<Environment>, std::string> GetEnvironmentReleases(
38+
const std::string& environment) const;
39+
cpp::result<std::vector<Environment>, std::string> GetInstalledEnvironments()
40+
const;
41+
cpp::result<std::vector<Environment>, std::string> GetDefaultEnvironment(
42+
const std::string& environment) const;
43+
cpp::result<std::vector<Environment>, std::string> SetDefaultEnvironment(
44+
const std::string& environment) const;
45+
46+
private:
47+
cpp::result<void, std::string> DownloadEnvironment(
48+
const std::string& environment, const std::string& version = "latest");
49+
cpp::result<std::pair<std::filesystem::path, bool>, std::string>
50+
GetEnvironmentDirPath(const std::string& environment);
51+
};

engine/utils/curl_utils.cc

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -242,8 +242,8 @@ cpp::result<YAML::Node, std::string> ReadRemoteYaml(const std::string& url) {
242242
}
243243
}
244244

245-
cpp::result<Json::Value, std::string> SimpleGetJson(const std::string& url,
246-
const int timeout) {
245+
cpp::result<Json::Value, std::string> SimpleGetJson(
246+
const std::string& url, const int timeout, std::optional<bool> recursive) {
247247
auto result = SimpleGet(url, timeout);
248248
if (result.has_error()) {
249249
CTL_ERR("Failed to get JSON from " + url + ": " + result.error());
@@ -257,11 +257,11 @@ cpp::result<Json::Value, std::string> SimpleGetJson(const std::string& url,
257257
" parsing error: " + reader.getFormattedErrorMessages());
258258
}
259259

260-
if (root.isArray()) {
260+
if (root.isArray() && recursive) {
261261
for (const auto& value : root) {
262262
if (value["type"].asString() == "directory") {
263263
auto temp =
264-
SimpleGetJson(url + "/" + value["path"].asString(), timeout);
264+
SimpleGetJson(url + "/" + value["path"].asString(), timeout, recursive);
265265
if (!temp.has_error()) {
266266
if (temp.value().isArray()) {
267267
for (const auto& item : temp.value()) {
@@ -273,14 +273,13 @@ cpp::result<Json::Value, std::string> SimpleGetJson(const std::string& url,
273273
}
274274
}
275275
}
276-
for (Json::ArrayIndex i = 0; i < root.size();) {
277-
if (root[i].isMember("type") && root[i]["type"] == "directory") {
278-
root.removeIndex(i, nullptr);
279-
} else {
280-
++i;
281-
}
276+
for (Json::ArrayIndex i = 0; i < root.size();) {
277+
if (root[i].isMember("type") && root[i]["type"] == "directory") {
278+
root.removeIndex(i, nullptr);
279+
} else {
280+
++i;
281+
}
282282
}
283-
284283
}
285284
return root;
286285
}

engine/utils/curl_utils.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,9 @@ cpp::result<YAML::Node, std::string> ReadRemoteYaml(const std::string& url);
3232
*
3333
* [timeout] is an optional parameter that specifies the timeout for the request. In second.
3434
*/
35-
cpp::result<Json::Value, std::string> SimpleGetJson(const std::string& url,
36-
const int timeout = -1);
35+
cpp::result<Json::Value, std::string> SimpleGetJson(
36+
const std::string& url, const int timeout = -1,
37+
std::optional<bool> recursive = true);
3738

3839
cpp::result<Json::Value, std::string> SimplePostJson(
3940
const std::string& url, const std::string& body = "");

engine/utils/environment_utils.h

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
#pragma once
2+
#include <json/json.h>
3+
#include <optional>
4+
#include <string>
5+
#include <unordered_set>
6+
#include <vector>
7+
#include "utils/curl_utils.h"
8+
#include "utils/result.hpp"
9+
namespace environment_utils {
10+
11+
constexpr const auto kBaseEnvironmentsUrl =
12+
"https://delta.jan.ai/environments/";
13+
14+
struct Environment {
15+
std::string type; // e.g., "python"
16+
std::string name; // e.g., "whispervq"
17+
std::string version; // e.g., "latest"
18+
std::string os; // e.g., "window", "linux"
19+
std::string arch; // e.g., "amd64"
20+
21+
// Convert Environment to JSON
22+
Json::Value ToJson() const {
23+
Json::Value json;
24+
json["type"] = type;
25+
json["name"] = name;
26+
json["version"] = version;
27+
json["os"] = os;
28+
json["arch"] = arch;
29+
return json;
30+
}
31+
32+
// Create Environment from JSON
33+
static cpp::result<Environment, std::string> FromJson(
34+
const Json::Value& json) {
35+
Environment env;
36+
37+
// Validate required fields
38+
const std::vector<std::string> required_fields = {"type", "name", "version",
39+
"os", "arch"};
40+
41+
for (const auto& field : required_fields) {
42+
if (!json.isMember(field) || json[field].asString().empty()) {
43+
return cpp::fail("Missing or empty required field: " + field);
44+
}
45+
}
46+
47+
env.type = json["type"].asString();
48+
env.name = json["name"].asString();
49+
env.version = json["version"].asString();
50+
env.os = json["os"].asString();
51+
env.arch = json["arch"].asString();
52+
53+
return env;
54+
}
55+
56+
// Method to generate full artifact URL
57+
std::string generateUrl() const {
58+
return kBaseEnvironmentsUrl + type + "/" + name + "/" + version + "/" +
59+
name + "-" + os + "-" + arch + ".zip";
60+
}
61+
62+
// Method to validate the environment structure
63+
bool isValid() const {
64+
return !type.empty() && !name.empty() && !version.empty() && !os.empty() &&
65+
!arch.empty();
66+
}
67+
};
68+
69+
// Utility function to parse URL components into an Environment struct
70+
cpp::result<Environment, std::string> parseEnvironmentUrl(
71+
const std::string& url) {
72+
Environment env;
73+
74+
size_t environments_pos = url.find("environments/");
75+
if (environments_pos == std::string::npos) {
76+
return cpp::fail("Invalid URL format");
77+
}
78+
79+
std::string remaining = url.substr(environments_pos + 13);
80+
std::vector<std::string> parts;
81+
size_t pos = 0;
82+
while ((pos = remaining.find('/')) != std::string::npos) {
83+
parts.push_back(remaining.substr(0, pos));
84+
remaining.erase(0, pos + 1);
85+
}
86+
parts.push_back(remaining);
87+
88+
if (parts.size() < 5) {
89+
return cpp::fail("Insufficient URL components");
90+
}
91+
92+
env.type = parts[0];
93+
env.name = parts[1];
94+
env.version = parts[2];
95+
96+
// Extract OS and arch from the filename
97+
std::string filename = parts[3];
98+
size_t os_sep = filename.find('-');
99+
size_t arch_sep = filename.find('-', os_sep + 1);
100+
101+
if (os_sep == std::string::npos || arch_sep == std::string::npos) {
102+
return cpp::fail("Cannot parse OS and architecture");
103+
}
104+
105+
env.os = filename.substr(os_sep + 1, arch_sep - os_sep - 1);
106+
env.arch = filename.substr(arch_sep + 1, filename.find('.') - arch_sep - 1);
107+
108+
return env;
109+
}
110+
111+
// Fetch environment names
112+
cpp::result<std::vector<std::string>, std::string> fetchEnvironmentNames(
113+
const std::string& type, int timeout = 30) {
114+
auto url = kBaseEnvironmentsUrl + type;
115+
auto json_result = curl_utils::SimpleGetJson(url, timeout, false);
116+
if (json_result.has_error()) {
117+
return cpp::fail(json_result.error());
118+
}
119+
120+
std::vector<std::string> environment_names;
121+
const Json::Value& root = json_result.value();
122+
123+
// Store unique environment names
124+
std::unordered_set<std::string> unique_names;
125+
126+
for (const auto& item : root) {
127+
if (item.isMember("path")) {
128+
environment_names.push_back(item["path"].asString());
129+
}
130+
}
131+
132+
return environment_names;
133+
}
134+
135+
// Get all versions for a specific environment
136+
cpp::result<std::vector<std::string>, std::string> fetchEnvironmentVersions(
137+
const std::string& base_url, const std::string& environment_name,
138+
int timeout = 30, bool recursive = true) {
139+
auto json_result = curl_utils::SimpleGetJson(
140+
base_url + "/" + environment_name, timeout, recursive);
141+
if (json_result.has_error()) {
142+
return cpp::fail(json_result.error());
143+
}
144+
145+
std::vector<std::string> versions;
146+
const Json::Value& root = json_result.value();
147+
148+
// Store unique versions
149+
std::unordered_set<std::string> unique_versions;
150+
151+
for (const auto& item : root) {
152+
if (item.isMember("path")) {
153+
auto url_parse_result = parseEnvironmentUrl(
154+
base_url + "/" + environment_name + "/" + item["path"].asString());
155+
if (!url_parse_result.has_error()) {
156+
const auto& env = url_parse_result.value();
157+
// Only add if not already present
158+
if (unique_versions.insert(env.version).second) {
159+
versions.push_back(env.version);
160+
}
161+
}
162+
}
163+
}
164+
165+
return versions;
166+
}
167+
168+
} // namespace environment_utils

0 commit comments

Comments
 (0)