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

Commit b18891d

Browse files
committed
feat: add messages api
1 parent 9622b91 commit b18891d

30 files changed

+3013
-4
lines changed

engine/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ else()
171171
endif()
172172

173173
aux_source_directory(controllers CTL_SRC)
174+
aux_source_directory(repositories REPO_SRC)
174175
aux_source_directory(services SERVICES_SRC)
175176
aux_source_directory(common COMMON_SRC)
176177
aux_source_directory(models MODEL_SRC)
@@ -181,7 +182,7 @@ aux_source_directory(migrations MIGR_SRC)
181182

182183
target_include_directories(${TARGET_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} )
183184

184-
target_sources(${TARGET_NAME} PRIVATE ${CONFIG_SRC} ${CTL_SRC} ${COMMON_SRC} ${SERVICES_SRC} ${DB_SRC} ${MIGR_SRC})
185+
target_sources(${TARGET_NAME} PRIVATE ${CONFIG_SRC} ${CTL_SRC} ${COMMON_SRC} ${SERVICES_SRC} ${DB_SRC} ${MIGR_SRC} ${REPO_SRC})
185186

186187
set_target_properties(${TARGET_NAME} PROPERTIES
187188
RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}

engine/cli/commands/engine_install_cmd.cc

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,6 @@ bool EngineInstallCmd::Exec(const std::string& engine,
177177
auto response = curl_utils::SimplePostJson(install_url.ToFullPath(),
178178
body.toStyledString());
179179
if (response.has_error()) {
180-
// TODO: namh refactor later
181180
Json::Value root;
182181
Json::Reader reader;
183182
if (!reader.parse(response.error(), root)) {
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#pragma once
2+
3+
#include "common/json_serializable.h"
4+
5+
namespace api_response {
6+
struct DeleteMessageResponse : JsonSerializable {
7+
std::string id;
8+
std::string object;
9+
bool deleted;
10+
11+
cpp::result<Json::Value, std::string> ToJson() override {
12+
Json::Value json;
13+
json["id"] = id;
14+
json["object"] = object;
15+
json["deleted"] = deleted;
16+
return json;
17+
}
18+
};
19+
} // namespace api_response

engine/common/json_serializable.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#pragma once
2+
3+
#include <json/value.h>
4+
#include "utils/result.hpp"
5+
6+
struct JsonSerializable {
7+
8+
virtual cpp::result<Json::Value, std::string> ToJson() = 0;
9+
10+
virtual ~JsonSerializable() = default;
11+
};

engine/common/message.h

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
#pragma once
2+
3+
#include <json/reader.h>
4+
#include <json/value.h>
5+
#include <json/writer.h>
6+
#include <cstdint>
7+
#include <string>
8+
#include "common/message_attachment.h"
9+
#include "common/message_attachment_factory.h"
10+
#include "common/message_content.h"
11+
#include "common/message_content_factory.h"
12+
#include "common/message_incomplete_detail.h"
13+
#include "common/message_role.h"
14+
#include "common/message_status.h"
15+
#include "common/variant_map.h"
16+
#include "json_serializable.h"
17+
#include "utils/logging_utils.h"
18+
#include "utils/result.hpp"
19+
20+
namespace ThreadMessage {
21+
22+
// Represents a message within a thread.
23+
struct Message : JsonSerializable {
24+
25+
// The identifier, which can be referenced in API endpoints.
26+
std::string id;
27+
28+
// The object type, which is always thread.message.
29+
std::string object = "thread.message";
30+
31+
// The Unix timestamp (in seconds) for when the message was created.
32+
uint32_t created_at;
33+
34+
// The thread ID that this message belongs to.
35+
std::string thread_id;
36+
37+
// The status of the message, which can be either in_progress, incomplete, or completed.
38+
Status status;
39+
40+
// On an incomplete message, details about why the message is incomplete.
41+
std::optional<IncompleteDetail> incomplete_details;
42+
43+
// The Unix timestamp (in seconds) for when the message was completed.
44+
std::optional<uint32_t> completed_at;
45+
46+
// The Unix timestamp (in seconds) for when the message was marked as incomplete.
47+
std::optional<uint32_t> incomplete_at;
48+
49+
Role role;
50+
51+
// The content of the message in array of text and/or images.
52+
std::vector<std::unique_ptr<Content>> content;
53+
54+
// If applicable, the ID of the assistant that authored this message.
55+
std::optional<std::string> assistant_id;
56+
57+
// The ID of the run associated with the creation of this message. Value is null when messages are created manually using the create message or create thread endpoints.
58+
std::optional<std::string> run_id;
59+
60+
// A list of files attached to the message, and the tools they were added to.
61+
std::optional<std::vector<Attachment>> attachments;
62+
63+
// 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.
64+
Cortex::VariantMap metadata;
65+
66+
static cpp::result<Message, std::string> FromJsonString(
67+
std::string&& json_str) {
68+
Json::Value root;
69+
Json::Reader reader;
70+
if (!reader.parse(json_str, root)) {
71+
return cpp::fail("Failed to parse JSON: " +
72+
reader.getFormattedErrorMessages());
73+
}
74+
75+
Message message;
76+
77+
try {
78+
message.id = std::move(root["id"].asString());
79+
message.object =
80+
std::move(root.get("object", "thread.message").asString());
81+
message.created_at = root["created_at"].asUInt();
82+
if (message.created_at == 0 && root["created"].asUInt64() != 0) {
83+
message.created_at = root["created"].asUInt64() / 1000;
84+
}
85+
message.thread_id = root["thread_id"].asString();
86+
message.status = StatusFromString(root["status"].asString());
87+
88+
message.incomplete_details =
89+
IncompleteDetail::FromJson(std::move(root["incomplete_details"]))
90+
.value();
91+
message.completed_at = root["completed_at"].asUInt();
92+
message.incomplete_at = root["incomplete_at"].asUInt();
93+
message.role = RoleFromString(root["role"].asString());
94+
message.content = ParseContents(std::move(root["content"])).value();
95+
96+
message.assistant_id = root["assistant_id"].asString();
97+
message.run_id = root["run_id"].asString();
98+
message.attachments =
99+
ParseAttachments(std::move(root["attachments"])).value();
100+
101+
if (root["metadata"].isObject() && !root["metadata"].empty()) {
102+
auto res = Cortex::ConvertJsonValueToMap(root["metadata"]);
103+
if (res.has_error()) {
104+
CTL_WRN("Failed to convert metadata to map: " + res.error());
105+
} else {
106+
message.metadata = res.value();
107+
}
108+
}
109+
110+
return message;
111+
} catch (const std::exception& e) {
112+
return cpp::fail(std::string("FromJsonString failed: ") + e.what());
113+
}
114+
}
115+
116+
cpp::result<std::string, std::string> ToSingleLineJsonString() {
117+
auto json_result = ToJson();
118+
if (json_result.has_error()) {
119+
return cpp::fail(json_result.error());
120+
}
121+
122+
Json::FastWriter writer;
123+
try {
124+
return writer.write(json_result.value());
125+
} catch (const std::exception& e) {
126+
return cpp::fail(std::string("Failed to write JSON: ") + e.what());
127+
}
128+
}
129+
130+
cpp::result<Json::Value, std::string> ToJson() override {
131+
try {
132+
Json::Value json;
133+
134+
json["id"] = id;
135+
json["object"] = object;
136+
json["created_at"] = created_at;
137+
json["thread_id"] = thread_id;
138+
json["status"] = StatusToString(status);
139+
140+
if (incomplete_details.has_value()) {
141+
if (auto it = incomplete_details->ToJson(); it.has_value()) {
142+
json["incomplete_details"] = it.value();
143+
} else {
144+
CTL_WRN("Failed to convert incomplete_details to json: " +
145+
it.error());
146+
}
147+
}
148+
if (completed_at.has_value() && completed_at.value() != 0) {
149+
json["completed_at"] = *completed_at;
150+
}
151+
if (incomplete_at.has_value() && incomplete_at.value() != 0) {
152+
json["incomplete_at"] = *incomplete_at;
153+
}
154+
155+
json["role"] = RoleToString(role);
156+
157+
Json::Value content_json_arr{Json::arrayValue};
158+
for (auto& child_content : content) {
159+
if (auto it = child_content->ToJson(); it.has_value()) {
160+
content_json_arr.append(it.value());
161+
} else {
162+
CTL_WRN("Failed to convert content to json: " + it.error());
163+
}
164+
}
165+
json["content"] = content_json_arr;
166+
if (assistant_id.has_value() && !assistant_id->empty()) {
167+
json["assistant_id"] = *assistant_id;
168+
}
169+
if (run_id.has_value() && !run_id->empty()) {
170+
json["run_id"] = *run_id;
171+
}
172+
if (attachments.has_value()) {
173+
Json::Value attachments_json_arr{Json::arrayValue};
174+
for (auto& attachment : *attachments) {
175+
if (auto it = attachment.ToJson(); it.has_value()) {
176+
attachments_json_arr.append(it.value());
177+
} else {
178+
CTL_WRN("Failed to convert attachment to json: " + it.error());
179+
}
180+
}
181+
json["attachments"] = attachments_json_arr;
182+
}
183+
184+
Json::Value metadata_json{Json::objectValue};
185+
for (const auto& [key, value] : metadata) {
186+
if (std::holds_alternative<bool>(value)) {
187+
metadata_json[key] = std::get<bool>(value);
188+
} else if (std::holds_alternative<uint64_t>(value)) {
189+
metadata_json[key] = std::get<uint64_t>(value);
190+
} else if (std::holds_alternative<double>(value)) {
191+
metadata_json[key] = std::get<double>(value);
192+
} else {
193+
metadata_json[key] = std::get<std::string>(value);
194+
}
195+
}
196+
json["metadata"] = metadata_json;
197+
198+
return json;
199+
} catch (const std::exception& e) {
200+
return cpp::fail(std::string("ToJson failed: ") + e.what());
201+
}
202+
}
203+
};
204+
}; // namespace ThreadMessage

engine/common/message_attachment.h

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
#pragma once
2+
3+
#include <json/reader.h>
4+
#include "common/json_serializable.h"
5+
6+
namespace ThreadMessage {
7+
8+
// The tools to add this file to.
9+
struct Tool {
10+
std::string type;
11+
12+
Tool(const std::string& type) : type{type} {}
13+
};
14+
15+
// The type of tool being defined: code_interpreter
16+
struct CodeInterpreter : Tool {
17+
CodeInterpreter() : Tool{"code_interpreter"} {}
18+
};
19+
20+
// The type of tool being defined: file_search
21+
struct FileSearch : Tool {
22+
FileSearch() : Tool{"file_search"} {}
23+
};
24+
25+
// A list of files attached to the message, and the tools they were added to.
26+
struct Attachment : JsonSerializable {
27+
28+
// The ID of the file to attach to the message.
29+
std::string file_id;
30+
31+
std::vector<Tool> tools;
32+
33+
cpp::result<Json::Value, std::string> ToJson() override {
34+
try {
35+
Json::Value json;
36+
json["file_id"] = file_id;
37+
Json::Value tools_json_arr{Json::arrayValue};
38+
for (auto& tool : tools) {
39+
Json::Value tool_json;
40+
tool_json["type"] = tool.type;
41+
tools_json_arr.append(tool_json);
42+
}
43+
json["tools"] = tools_json_arr;
44+
return json;
45+
} catch (const std::exception& e) {
46+
return cpp::fail(std::string("ToJson failed: ") + e.what());
47+
}
48+
}
49+
};
50+
}; // namespace ThreadMessage
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#include <optional>
2+
#include "common/message_attachment.h"
3+
#include "utils/result.hpp"
4+
5+
namespace ThreadMessage {
6+
inline cpp::result<Attachment, std::string> ParseAttachment(
7+
Json::Value&& json) {
8+
if (json.empty()) {
9+
return cpp::fail("Json string is empty");
10+
}
11+
12+
Attachment attachment;
13+
attachment.file_id = json["file_id"].asString();
14+
15+
std::vector<Tool> tools{};
16+
if (json["tools"].isArray()) {
17+
for (auto& tool_json : json["tools"]) {
18+
Tool tool{tool_json["type"].asString()};
19+
tools.push_back(tool);
20+
}
21+
}
22+
attachment.tools = tools;
23+
24+
return attachment;
25+
}
26+
27+
inline cpp::result<std::optional<std::vector<Attachment>>, std::string>
28+
ParseAttachments(Json::Value&& json) {
29+
if (json.empty()) {
30+
// still count as success
31+
return std::nullopt;
32+
}
33+
if (!json.isArray()) {
34+
return cpp::fail("Json is not an array");
35+
}
36+
37+
std::vector<Attachment> attachments;
38+
for (auto& attachment_json : json) {
39+
auto attachment = ParseAttachment(std::move(attachment_json));
40+
if (attachment.has_error()) {
41+
return cpp::fail(attachment.error());
42+
}
43+
attachments.push_back(attachment.value());
44+
}
45+
46+
return attachments;
47+
}
48+
}; // namespace ThreadMessage

engine/common/message_content.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#pragma once
2+
3+
#include <string>
4+
#include "common/json_serializable.h"
5+
6+
namespace ThreadMessage {
7+
8+
struct Content : JsonSerializable {
9+
std::string type;
10+
11+
Content(const std::string& type) : type{type} {}
12+
13+
virtual ~Content() = default;
14+
};
15+
}; // namespace ThreadMessage

0 commit comments

Comments
 (0)