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

Commit 7b6126f

Browse files
committed
feat: add messages api
1 parent 87b59d4 commit 7b6126f

27 files changed

+1703
-4
lines changed

engine/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ else()
167167
endif()
168168

169169
aux_source_directory(controllers CTL_SRC)
170+
aux_source_directory(repositories REPO_SRC)
170171
aux_source_directory(services SERVICES_SRC)
171172
aux_source_directory(common COMMON_SRC)
172173
aux_source_directory(models MODEL_SRC)
@@ -177,7 +178,7 @@ aux_source_directory(migrations MIGR_SRC)
177178

178179
target_include_directories(${TARGET_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} )
179180

180-
target_sources(${TARGET_NAME} PRIVATE ${CONFIG_SRC} ${CTL_SRC} ${COMMON_SRC} ${SERVICES_SRC} ${DB_SRC} ${MIGR_SRC})
181+
target_sources(${TARGET_NAME} PRIVATE ${CONFIG_SRC} ${CTL_SRC} ${COMMON_SRC} ${SERVICES_SRC} ${DB_SRC} ${MIGR_SRC} ${REPO_SRC})
181182

182183
set_target_properties(${TARGET_NAME} PROPERTIES
183184
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 ApiResponseDto {
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 ApiResponseDto

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