-
Notifications
You must be signed in to change notification settings - Fork 134
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1720 from janhq/j/add-message
feat: add messages api
- Loading branch information
Showing
31 changed files
with
3,127 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
#pragma once | ||
|
||
#include "common/json_serializable.h" | ||
|
||
namespace api_response { | ||
struct DeleteMessageResponse : JsonSerializable { | ||
std::string id; | ||
std::string object; | ||
bool deleted; | ||
|
||
cpp::result<Json::Value, std::string> ToJson() override { | ||
Json::Value json; | ||
json["id"] = id; | ||
json["object"] = object; | ||
json["deleted"] = deleted; | ||
return json; | ||
} | ||
}; | ||
} // namespace api_response |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
#pragma once | ||
|
||
#include <json/value.h> | ||
#include "utils/result.hpp" | ||
|
||
struct JsonSerializable { | ||
|
||
virtual cpp::result<Json::Value, std::string> ToJson() = 0; | ||
|
||
virtual ~JsonSerializable() = default; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,213 @@ | ||
#pragma once | ||
|
||
#include <json/reader.h> | ||
#include <json/value.h> | ||
#include <json/writer.h> | ||
#include <cstdint> | ||
#include <string> | ||
#include "common/message_attachment.h" | ||
#include "common/message_attachment_factory.h" | ||
#include "common/message_content.h" | ||
#include "common/message_content_factory.h" | ||
#include "common/message_incomplete_detail.h" | ||
#include "common/message_role.h" | ||
#include "common/message_status.h" | ||
#include "common/variant_map.h" | ||
#include "json_serializable.h" | ||
#include "utils/logging_utils.h" | ||
#include "utils/result.hpp" | ||
|
||
namespace ThreadMessage { | ||
|
||
// Represents a message within a thread. | ||
struct Message : JsonSerializable { | ||
Message() = default; | ||
|
||
Message(Message&&) = default; | ||
|
||
Message& operator=(Message&&) = default; | ||
|
||
Message(const Message&) = delete; | ||
|
||
Message& operator=(const Message&) = delete; | ||
|
||
// The identifier, which can be referenced in API endpoints. | ||
std::string id; | ||
|
||
// The object type, which is always thread.message. | ||
std::string object = "thread.message"; | ||
|
||
// The Unix timestamp (in seconds) for when the message was created. | ||
uint32_t created_at; | ||
|
||
// The thread ID that this message belongs to. | ||
std::string thread_id; | ||
|
||
// The status of the message, which can be either in_progress, incomplete, or completed. | ||
Status status; | ||
|
||
// On an incomplete message, details about why the message is incomplete. | ||
std::optional<IncompleteDetail> incomplete_details; | ||
|
||
// The Unix timestamp (in seconds) for when the message was completed. | ||
std::optional<uint32_t> completed_at; | ||
|
||
// The Unix timestamp (in seconds) for when the message was marked as incomplete. | ||
std::optional<uint32_t> incomplete_at; | ||
|
||
Role role; | ||
|
||
// The content of the message in array of text and/or images. | ||
std::vector<std::unique_ptr<Content>> content; | ||
|
||
// If applicable, the ID of the assistant that authored this message. | ||
std::optional<std::string> assistant_id; | ||
|
||
// 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. | ||
std::optional<std::string> run_id; | ||
|
||
// A list of files attached to the message, and the tools they were added to. | ||
std::optional<std::vector<Attachment>> attachments; | ||
|
||
// 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. | ||
Cortex::VariantMap metadata; | ||
|
||
static cpp::result<Message, std::string> FromJsonString( | ||
std::string&& json_str) { | ||
Json::Value root; | ||
Json::Reader reader; | ||
if (!reader.parse(json_str, root)) { | ||
return cpp::fail("Failed to parse JSON: " + | ||
reader.getFormattedErrorMessages()); | ||
} | ||
|
||
Message message; | ||
|
||
try { | ||
message.id = std::move(root["id"].asString()); | ||
message.object = | ||
std::move(root.get("object", "thread.message").asString()); | ||
message.created_at = root["created_at"].asUInt(); | ||
if (message.created_at == 0 && root["created"].asUInt64() != 0) { | ||
message.created_at = root["created"].asUInt64() / 1000; | ||
} | ||
message.thread_id = std::move(root["thread_id"].asString()); | ||
message.status = StatusFromString(std::move(root["status"].asString())); | ||
|
||
message.incomplete_details = | ||
IncompleteDetail::FromJson(std::move(root["incomplete_details"])) | ||
.value(); | ||
message.completed_at = root["completed_at"].asUInt(); | ||
message.incomplete_at = root["incomplete_at"].asUInt(); | ||
message.role = RoleFromString(std::move(root["role"].asString())); | ||
message.content = ParseContents(std::move(root["content"])).value(); | ||
|
||
message.assistant_id = std::move(root["assistant_id"].asString()); | ||
message.run_id = std::move(root["run_id"].asString()); | ||
message.attachments = | ||
ParseAttachments(std::move(root["attachments"])).value(); | ||
|
||
if (root["metadata"].isObject() && !root["metadata"].empty()) { | ||
auto res = Cortex::ConvertJsonValueToMap(root["metadata"]); | ||
if (res.has_error()) { | ||
CTL_WRN("Failed to convert metadata to map: " + res.error()); | ||
} else { | ||
message.metadata = res.value(); | ||
} | ||
} | ||
|
||
return message; | ||
} catch (const std::exception& e) { | ||
return cpp::fail(std::string("FromJsonString failed: ") + e.what()); | ||
} | ||
} | ||
|
||
cpp::result<std::string, std::string> ToSingleLineJsonString() { | ||
auto json_result = ToJson(); | ||
if (json_result.has_error()) { | ||
return cpp::fail(json_result.error()); | ||
} | ||
|
||
Json::FastWriter writer; | ||
try { | ||
return writer.write(json_result.value()); | ||
} catch (const std::exception& e) { | ||
return cpp::fail(std::string("Failed to write JSON: ") + e.what()); | ||
} | ||
} | ||
|
||
cpp::result<Json::Value, std::string> ToJson() override { | ||
try { | ||
Json::Value json; | ||
|
||
json["id"] = id; | ||
json["object"] = object; | ||
json["created_at"] = created_at; | ||
json["thread_id"] = thread_id; | ||
json["status"] = StatusToString(status); | ||
|
||
if (incomplete_details.has_value()) { | ||
if (auto it = incomplete_details->ToJson(); it.has_value()) { | ||
json["incomplete_details"] = it.value(); | ||
} else { | ||
CTL_WRN("Failed to convert incomplete_details to json: " + | ||
it.error()); | ||
} | ||
} | ||
if (completed_at.has_value() && completed_at.value() != 0) { | ||
json["completed_at"] = *completed_at; | ||
} | ||
if (incomplete_at.has_value() && incomplete_at.value() != 0) { | ||
json["incomplete_at"] = *incomplete_at; | ||
} | ||
|
||
json["role"] = RoleToString(role); | ||
|
||
Json::Value content_json_arr{Json::arrayValue}; | ||
for (auto& child_content : content) { | ||
if (auto it = child_content->ToJson(); it.has_value()) { | ||
content_json_arr.append(it.value()); | ||
} else { | ||
CTL_WRN("Failed to convert content to json: " + it.error()); | ||
} | ||
} | ||
json["content"] = content_json_arr; | ||
if (assistant_id.has_value() && !assistant_id->empty()) { | ||
json["assistant_id"] = *assistant_id; | ||
} | ||
if (run_id.has_value() && !run_id->empty()) { | ||
json["run_id"] = *run_id; | ||
} | ||
if (attachments.has_value()) { | ||
Json::Value attachments_json_arr{Json::arrayValue}; | ||
for (auto& attachment : *attachments) { | ||
if (auto it = attachment.ToJson(); it.has_value()) { | ||
attachments_json_arr.append(it.value()); | ||
} else { | ||
CTL_WRN("Failed to convert attachment to json: " + it.error()); | ||
} | ||
} | ||
json["attachments"] = attachments_json_arr; | ||
} | ||
|
||
Json::Value metadata_json{Json::objectValue}; | ||
for (const auto& [key, value] : metadata) { | ||
if (std::holds_alternative<bool>(value)) { | ||
metadata_json[key] = std::get<bool>(value); | ||
} else if (std::holds_alternative<uint64_t>(value)) { | ||
metadata_json[key] = std::get<uint64_t>(value); | ||
} else if (std::holds_alternative<double>(value)) { | ||
metadata_json[key] = std::get<double>(value); | ||
} else { | ||
metadata_json[key] = std::get<std::string>(value); | ||
} | ||
} | ||
json["metadata"] = metadata_json; | ||
|
||
return json; | ||
} catch (const std::exception& e) { | ||
return cpp::fail(std::string("ToJson failed: ") + e.what()); | ||
} | ||
} | ||
}; | ||
}; // namespace ThreadMessage |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
#pragma once | ||
|
||
#include <json/reader.h> | ||
#include "common/json_serializable.h" | ||
|
||
namespace ThreadMessage { | ||
|
||
// The tools to add this file to. | ||
struct Tool { | ||
std::string type; | ||
|
||
Tool(const std::string& type) : type{type} {} | ||
}; | ||
|
||
// The type of tool being defined: code_interpreter | ||
struct CodeInterpreter : Tool { | ||
CodeInterpreter() : Tool{"code_interpreter"} {} | ||
}; | ||
|
||
// The type of tool being defined: file_search | ||
struct FileSearch : Tool { | ||
FileSearch() : Tool{"file_search"} {} | ||
}; | ||
|
||
// A list of files attached to the message, and the tools they were added to. | ||
struct Attachment : JsonSerializable { | ||
|
||
// The ID of the file to attach to the message. | ||
std::string file_id; | ||
|
||
std::vector<Tool> tools; | ||
|
||
cpp::result<Json::Value, std::string> ToJson() override { | ||
try { | ||
Json::Value json; | ||
json["file_id"] = file_id; | ||
Json::Value tools_json_arr{Json::arrayValue}; | ||
for (auto& tool : tools) { | ||
Json::Value tool_json; | ||
tool_json["type"] = tool.type; | ||
tools_json_arr.append(tool_json); | ||
} | ||
json["tools"] = tools_json_arr; | ||
return json; | ||
} catch (const std::exception& e) { | ||
return cpp::fail(std::string("ToJson failed: ") + e.what()); | ||
} | ||
} | ||
}; | ||
}; // namespace ThreadMessage |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
#include <optional> | ||
#include "common/message_attachment.h" | ||
#include "utils/result.hpp" | ||
|
||
namespace ThreadMessage { | ||
inline cpp::result<Attachment, std::string> ParseAttachment( | ||
Json::Value&& json) { | ||
if (json.empty()) { | ||
return cpp::fail("Json string is empty"); | ||
} | ||
|
||
Attachment attachment; | ||
attachment.file_id = json["file_id"].asString(); | ||
|
||
std::vector<Tool> tools{}; | ||
if (json["tools"].isArray()) { | ||
for (auto& tool_json : json["tools"]) { | ||
Tool tool{tool_json["type"].asString()}; | ||
tools.push_back(tool); | ||
} | ||
} | ||
attachment.tools = tools; | ||
|
||
return attachment; | ||
} | ||
|
||
inline cpp::result<std::optional<std::vector<Attachment>>, std::string> | ||
ParseAttachments(Json::Value&& json) { | ||
if (json.empty()) { | ||
// still count as success | ||
return std::nullopt; | ||
} | ||
if (!json.isArray()) { | ||
return cpp::fail("Json is not an array"); | ||
} | ||
|
||
std::vector<Attachment> attachments; | ||
for (auto& attachment_json : json) { | ||
auto attachment = ParseAttachment(std::move(attachment_json)); | ||
if (attachment.has_error()) { | ||
return cpp::fail(attachment.error()); | ||
} | ||
attachments.push_back(attachment.value()); | ||
} | ||
|
||
return attachments; | ||
} | ||
}; // namespace ThreadMessage |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
#pragma once | ||
|
||
#include <string> | ||
#include "common/json_serializable.h" | ||
|
||
namespace ThreadMessage { | ||
|
||
struct Content : JsonSerializable { | ||
std::string type; | ||
|
||
Content(const std::string& type) : type{type} {} | ||
|
||
Content(const Content&) = delete; | ||
|
||
Content& operator=(const Content&) = delete; | ||
|
||
Content(Content&&) noexcept = default; | ||
|
||
Content& operator=(Content&&) noexcept = default; | ||
|
||
virtual ~Content() = default; | ||
}; | ||
}; // namespace ThreadMessage |
Oops, something went wrong.