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

Commit 6f9e8d4

Browse files
committed
add backward support
1 parent 33d1ebe commit 6f9e8d4

File tree

9 files changed

+237
-10
lines changed

9 files changed

+237
-10
lines changed

engine/common/message.h

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,20 @@
1919

2020
namespace OpenAi {
2121

22+
inline std::string ExtractFileId(const std::string& path) {
23+
// Handle both forward and backward slashes
24+
size_t lastSlash = path.find_last_of("/\\");
25+
if (lastSlash == std::string::npos)
26+
return "";
27+
28+
std::string filename = path.substr(lastSlash + 1);
29+
size_t dotPosition = filename.find('.');
30+
if (dotPosition == std::string::npos)
31+
return "";
32+
33+
return filename.substr(0, dotPosition);
34+
}
35+
2236
// Represents a message within a thread.
2337
struct Message : JsonSerializable {
2438
Message() = default;
@@ -70,6 +84,12 @@ struct Message : JsonSerializable {
7084
// Set of 16 key-value pairs that can be attached to an object. This can be useful for storing additional information about the object in a structured format. Keys can be a maximum of 64 characters long and values can be a maximum of 512 characters long.
7185
Cortex::VariantMap metadata;
7286

87+
// deprecated. remove in the future
88+
std::optional<std::string> attach_filename;
89+
std::optional<uint64_t> size;
90+
std::optional<std::string> rel_path;
91+
// end deprecated
92+
7393
static cpp::result<Message, std::string> FromJsonString(
7494
std::string&& json_str) {
7595
Json::Value root;
@@ -98,7 +118,6 @@ struct Message : JsonSerializable {
98118
message.completed_at = root["completed_at"].asUInt();
99119
message.incomplete_at = root["incomplete_at"].asUInt();
100120
message.role = RoleFromString(std::move(root["role"].asString()));
101-
message.content = ParseContents(std::move(root["content"])).value();
102121

103122
message.assistant_id = std::move(root["assistant_id"].asString());
104123
message.run_id = std::move(root["run_id"].asString());
@@ -114,6 +133,54 @@ struct Message : JsonSerializable {
114133
}
115134
}
116135

136+
if (root.isMember("content")) {
137+
if (root["content"].isArray() && !root["content"].empty()) {
138+
if (root["content"][0]["type"].asString() == "text") {
139+
message.content = ParseContents(std::move(root["content"])).value();
140+
} else {
141+
// deprecated, for supporting jan and should be removed in the future
142+
// check if annotations is empty
143+
if (!root["content"][0]["text"]["annotations"].empty()) {
144+
// parse attachment
145+
Json::Value attachments_json_array{Json::arrayValue};
146+
Json::Value attachment;
147+
attachment["file_id"] = ExtractFileId(
148+
root["content"][0]["text"]["annotations"][0].asString());
149+
150+
Json::Value tools_json_array{Json::arrayValue};
151+
Json::Value tool;
152+
tool["type"] = "file_search";
153+
tools_json_array.append(tool);
154+
155+
attachment["tools"] = tools_json_array;
156+
attachment["file_id"] = attachments_json_array.append(attachment);
157+
158+
message.attachments =
159+
ParseAttachments(std::move(attachments_json_array)).value();
160+
161+
message.attach_filename =
162+
root["content"][0]["text"]["name"].asString();
163+
message.size = root["content"][0]["text"]["size"].asUInt64();
164+
message.rel_path =
165+
root["content"][0]["text"]["annotations"][0].asString();
166+
}
167+
168+
// parse content
169+
Json::Value contents_json_array{Json::arrayValue};
170+
Json::Value content;
171+
Json::Value content_text;
172+
Json::Value empty_annotations{Json::arrayValue};
173+
content["type"] = "text";
174+
content_text["value"] = root["content"][0]["text"]["value"];
175+
content_text["annotations"] = empty_annotations;
176+
content["text"] = content_text;
177+
contents_json_array.append(content);
178+
message.content =
179+
ParseContents(std::move(contents_json_array)).value();
180+
}
181+
}
182+
}
183+
117184
return message;
118185
} catch (const std::exception& e) {
119186
return cpp::fail(std::string("FromJsonString failed: ") + e.what());

engine/common/repository/file_repository.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ class FileRepository {
1919
virtual cpp::result<std::pair<std::unique_ptr<char[]>, size_t>, std::string>
2020
RetrieveFileContent(const std::string& file_id) const = 0;
2121

22+
virtual cpp::result<std::pair<std::unique_ptr<char[]>, size_t>, std::string>
23+
RetrieveFileContentByPath(const std::string& path) const = 0;
24+
2225
virtual cpp::result<void, std::string> DeleteFile(
2326
const std::string& file_id) = 0;
2427

engine/controllers/files.cc

Lines changed: 115 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,62 @@ void Files::ListFiles(const HttpRequestPtr& req,
9595

9696
void Files::RetrieveFile(const HttpRequestPtr& req,
9797
std::function<void(const HttpResponsePtr&)>&& callback,
98-
const std::string& file_id) const {
98+
const std::string& file_id,
99+
std::optional<std::string> thread_id) const {
100+
// this code part is for backward compatible. remove it later on
101+
if (thread_id.has_value()) {
102+
auto msg_res =
103+
message_service_->RetrieveMessage(thread_id.value(), file_id);
104+
if (msg_res.has_error()) {
105+
Json::Value ret;
106+
ret["message"] = msg_res.error();
107+
auto resp = cortex_utils::CreateCortexHttpJsonResponse(ret);
108+
resp->setStatusCode(k400BadRequest);
109+
callback(resp);
110+
return;
111+
}
112+
113+
if (msg_res->attachments->empty()) {
114+
auto res = file_service_->RetrieveFile(file_id);
115+
if (res.has_error()) {
116+
Json::Value ret;
117+
ret["message"] = res.error();
118+
auto resp = cortex_utils::CreateCortexHttpJsonResponse(ret);
119+
resp->setStatusCode(k400BadRequest);
120+
callback(resp);
121+
return;
122+
}
123+
124+
auto resp =
125+
cortex_utils::CreateCortexHttpJsonResponse(res->ToJson().value());
126+
resp->setStatusCode(k200OK);
127+
callback(resp);
128+
return;
129+
} else {
130+
if (!msg_res->attach_filename.has_value() || !msg_res->size.has_value()) {
131+
Json::Value ret;
132+
ret["message"] = "File not found or had been removed!";
133+
auto resp = cortex_utils::CreateCortexHttpJsonResponse(ret);
134+
resp->setStatusCode(k404NotFound);
135+
callback(resp);
136+
return;
137+
}
138+
139+
Json::Value ret;
140+
ret["object"] = "file";
141+
ret["created_at"] = msg_res->created_at;
142+
ret["filename"] = msg_res->attach_filename.value();
143+
ret["bytes"] = msg_res->size.value();
144+
ret["id"] = msg_res->id;
145+
ret["purpose"] = "assistants";
146+
147+
auto resp = cortex_utils::CreateCortexHttpJsonResponse(ret);
148+
resp->setStatusCode(k200OK);
149+
callback(resp);
150+
return;
151+
}
152+
}
153+
99154
auto res = file_service_->RetrieveFile(file_id);
100155
if (res.has_error()) {
101156
Json::Value ret;
@@ -137,7 +192,65 @@ void Files::DeleteFile(const HttpRequestPtr& req,
137192
void Files::RetrieveFileContent(
138193
const HttpRequestPtr& req,
139194
std::function<void(const HttpResponsePtr&)>&& callback,
140-
const std::string& file_id) {
195+
const std::string& file_id, std::optional<std::string> thread_id) {
196+
if (thread_id.has_value()) {
197+
auto msg_res =
198+
message_service_->RetrieveMessage(thread_id.value(), file_id);
199+
if (msg_res.has_error()) {
200+
Json::Value ret;
201+
ret["message"] = msg_res.error();
202+
auto resp = cortex_utils::CreateCortexHttpJsonResponse(ret);
203+
resp->setStatusCode(k400BadRequest);
204+
callback(resp);
205+
return;
206+
}
207+
208+
if (msg_res->attachments->empty()) {
209+
auto res = file_service_->RetrieveFileContent(file_id);
210+
if (res.has_error()) {
211+
Json::Value ret;
212+
ret["message"] = res.error();
213+
auto resp = cortex_utils::CreateCortexHttpJsonResponse(ret);
214+
resp->setStatusCode(k400BadRequest);
215+
callback(resp);
216+
return;
217+
}
218+
219+
auto [buffer, size] = std::move(res.value());
220+
auto resp = HttpResponse::newHttpResponse();
221+
resp->setBody(std::string(buffer.get(), size));
222+
resp->setContentTypeCode(CT_APPLICATION_OCTET_STREAM);
223+
callback(resp);
224+
} else {
225+
if (!msg_res->rel_path.has_value()) {
226+
Json::Value ret;
227+
ret["message"] = "File not found or had been removed";
228+
auto resp = cortex_utils::CreateCortexHttpJsonResponse(ret);
229+
resp->setStatusCode(k400BadRequest);
230+
callback(resp);
231+
return;
232+
}
233+
234+
auto content_res =
235+
file_service_->RetrieveFileContentByPath(msg_res->rel_path.value());
236+
237+
if (content_res.has_error()) {
238+
Json::Value ret;
239+
ret["message"] = content_res.error();
240+
auto resp = cortex_utils::CreateCortexHttpJsonResponse(ret);
241+
resp->setStatusCode(k400BadRequest);
242+
callback(resp);
243+
return;
244+
}
245+
246+
auto [buffer, size] = std::move(content_res.value());
247+
auto resp = HttpResponse::newHttpResponse();
248+
resp->setBody(std::string(buffer.get(), size));
249+
resp->setContentTypeCode(CT_APPLICATION_OCTET_STREAM);
250+
callback(resp);
251+
}
252+
}
253+
141254
auto res = file_service_->RetrieveFileContent(file_id);
142255
if (res.has_error()) {
143256
Json::Value ret;

engine/controllers/files.h

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include <trantor/utils/Logger.h>
55
#include <optional>
66
#include "services/file_service.h"
7+
#include "services/message_service.h"
78

89
using namespace drogon;
910

@@ -12,7 +13,8 @@ class Files : public drogon::HttpController<Files, false> {
1213
METHOD_LIST_BEGIN
1314
ADD_METHOD_TO(Files::UploadFile, "/v1/files", Options, Post);
1415

15-
ADD_METHOD_TO(Files::RetrieveFile, "/v1/files/{file_id}", Get);
16+
ADD_METHOD_TO(Files::RetrieveFile, "/v1/files/{file_id}?thread={thread_id}",
17+
Get);
1618

1719
ADD_METHOD_TO(
1820
Files::ListFiles,
@@ -21,12 +23,14 @@ class Files : public drogon::HttpController<Files, false> {
2123

2224
ADD_METHOD_TO(Files::DeleteFile, "/v1/files/{file_id}", Options, Delete);
2325

24-
ADD_METHOD_TO(Files::RetrieveFileContent, "/v1/files/{file_id}/content", Get);
26+
ADD_METHOD_TO(Files::RetrieveFileContent,
27+
"/v1/files/{file_id}/content?thread={thread_id}", Get);
2528

2629
METHOD_LIST_END
2730

28-
explicit Files(std::shared_ptr<FileService> file_service)
29-
: file_service_{file_service} {}
31+
explicit Files(std::shared_ptr<FileService> file_service,
32+
std::shared_ptr<MessageService> msg_service)
33+
: file_service_{file_service}, message_service_{msg_service} {}
3034

3135
void UploadFile(const HttpRequestPtr& req,
3236
std::function<void(const HttpResponsePtr&)>&& callback);
@@ -40,7 +44,8 @@ class Files : public drogon::HttpController<Files, false> {
4044

4145
void RetrieveFile(const HttpRequestPtr& req,
4246
std::function<void(const HttpResponsePtr&)>&& callback,
43-
const std::string& file_id) const;
47+
const std::string& file_id,
48+
std::optional<std::string> thread_id) const;
4449

4550
void DeleteFile(const HttpRequestPtr& req,
4651
std::function<void(const HttpResponsePtr&)>&& callback,
@@ -49,8 +54,9 @@ class Files : public drogon::HttpController<Files, false> {
4954
void RetrieveFileContent(
5055
const HttpRequestPtr& req,
5156
std::function<void(const HttpResponsePtr&)>&& callback,
52-
const std::string& file_id);
57+
const std::string& file_id, std::optional<std::string> thread_id);
5358

5459
private:
5560
std::shared_ptr<FileService> file_service_;
61+
std::shared_ptr<MessageService> message_service_;
5662
};

engine/main.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ void RunServer(std::optional<int> port, bool ignore_cout) {
149149
file_watcher_srv->start();
150150

151151
// initialize custom controllers
152-
auto file_ctl = std::make_shared<Files>(file_srv);
152+
auto file_ctl = std::make_shared<Files>(file_srv, message_srv);
153153
auto assistant_ctl = std::make_shared<Assistants>(assistant_srv);
154154
auto thread_ctl = std::make_shared<Threads>(thread_srv, message_srv);
155155
auto message_ctl = std::make_shared<Messages>(message_srv);

engine/repositories/file_fs_repository.cc

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,30 @@ FileFsRepository::RetrieveFileContent(const std::string& file_id) const {
176176
}
177177
}
178178

179+
cpp::result<std::pair<std::unique_ptr<char[]>, size_t>, std::string>
180+
FileFsRepository::RetrieveFileContentByPath(const std::string& path) const {
181+
auto file_path = data_folder_path_ / path;
182+
std::lock_guard<std::mutex> lock(fs_mutex_);
183+
if (!std::filesystem::exists(file_path)) {
184+
return cpp::fail("File content not found: " + path);
185+
}
186+
187+
try {
188+
size_t size = std::filesystem::file_size(file_path);
189+
auto buffer = std::make_unique<char[]>(size);
190+
191+
std::ifstream file(file_path, std::ios::binary);
192+
if (!file.read(buffer.get(), size)) {
193+
return cpp::fail("Failed to read file: " + file_path.string());
194+
}
195+
196+
return std::make_pair(std::move(buffer), size);
197+
} catch (const std::exception& e) {
198+
CTL_ERR("Failed to retrieve file content: " << e.what());
199+
return cpp::fail("Failed to retrieve file content");
200+
}
201+
}
202+
179203
cpp::result<void, std::string> FileFsRepository::DeleteFile(
180204
const std::string& file_id) {
181205
CTL_INF("Deleting file: " + file_id);

engine/repositories/file_fs_repository.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ class FileFsRepository : public FileRepository {
2424
cpp::result<std::pair<std::unique_ptr<char[]>, size_t>, std::string>
2525
RetrieveFileContent(const std::string& file_id) const override;
2626

27+
cpp::result<std::pair<std::unique_ptr<char[]>, size_t>, std::string>
28+
RetrieveFileContentByPath(const std::string& path) const override;
29+
2730
cpp::result<void, std::string> DeleteFile(
2831
const std::string& file_id) override;
2932

engine/services/file_service.cc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,8 @@ cpp::result<std::pair<std::unique_ptr<char[]>, size_t>, std::string>
4848
FileService::RetrieveFileContent(const std::string& file_id) const {
4949
return file_repository_->RetrieveFileContent(file_id);
5050
}
51+
52+
cpp::result<std::pair<std::unique_ptr<char[]>, size_t>, std::string>
53+
FileService::RetrieveFileContentByPath(const std::string& path) const {
54+
return file_repository_->RetrieveFileContentByPath(path);
55+
}

engine/services/file_service.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ class FileService {
2626
cpp::result<std::pair<std::unique_ptr<char[]>, size_t>, std::string>
2727
RetrieveFileContent(const std::string& file_id) const;
2828

29+
/**
30+
* For getting file content by **relative** path.
31+
*/
32+
cpp::result<std::pair<std::unique_ptr<char[]>, size_t>, std::string>
33+
RetrieveFileContentByPath(const std::string& path) const;
34+
2935
explicit FileService(std::shared_ptr<FileRepository> file_repository)
3036
: file_repository_{file_repository} {}
3137

0 commit comments

Comments
 (0)