mirror of
https://github.com/Palm1r/QodeAssist.git
synced 2026-02-23 07:23:02 -05:00
feat: Add OpenAI Responses API (#282)
* feat: Add OpenAI Responses API * fix: Make temperature optional * chore: Increase default value of max tokens
This commit is contained in:
54
providers/OpenAIResponses/CancelResponseRequest.hpp
Normal file
54
providers/OpenAIResponses/CancelResponseRequest.hpp
Normal file
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (C) 2024-2025 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
|
||||
namespace QodeAssist::OpenAIResponses {
|
||||
|
||||
struct CancelResponseRequest
|
||||
{
|
||||
QString responseId;
|
||||
|
||||
QString buildUrl(const QString &baseUrl) const
|
||||
{
|
||||
return QString("%1/v1/responses/%2/cancel").arg(baseUrl, responseId);
|
||||
}
|
||||
|
||||
bool isValid() const { return !responseId.isEmpty(); }
|
||||
};
|
||||
|
||||
class CancelResponseRequestBuilder
|
||||
{
|
||||
public:
|
||||
CancelResponseRequestBuilder &setResponseId(const QString &id)
|
||||
{
|
||||
m_request.responseId = id;
|
||||
return *this;
|
||||
}
|
||||
|
||||
CancelResponseRequest build() const { return m_request; }
|
||||
|
||||
private:
|
||||
CancelResponseRequest m_request;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::OpenAIResponses
|
||||
|
||||
69
providers/OpenAIResponses/DeleteResponseRequest.hpp
Normal file
69
providers/OpenAIResponses/DeleteResponseRequest.hpp
Normal file
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright (C) 2024-2025 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QString>
|
||||
|
||||
namespace QodeAssist::OpenAIResponses {
|
||||
|
||||
struct DeleteResponseRequest
|
||||
{
|
||||
QString responseId;
|
||||
|
||||
QString buildUrl(const QString &baseUrl) const
|
||||
{
|
||||
return QString("%1/v1/responses/%2").arg(baseUrl, responseId);
|
||||
}
|
||||
|
||||
bool isValid() const { return !responseId.isEmpty(); }
|
||||
};
|
||||
|
||||
class DeleteResponseRequestBuilder
|
||||
{
|
||||
public:
|
||||
DeleteResponseRequestBuilder &setResponseId(const QString &id)
|
||||
{
|
||||
m_request.responseId = id;
|
||||
return *this;
|
||||
}
|
||||
|
||||
DeleteResponseRequest build() const { return m_request; }
|
||||
|
||||
private:
|
||||
DeleteResponseRequest m_request;
|
||||
};
|
||||
|
||||
struct DeleteResponseResult
|
||||
{
|
||||
bool success = false;
|
||||
QString message;
|
||||
|
||||
static DeleteResponseResult fromJson(const QJsonObject &obj)
|
||||
{
|
||||
DeleteResponseResult result;
|
||||
result.success = obj["success"].toBool();
|
||||
result.message = obj["message"].toString();
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::OpenAIResponses
|
||||
|
||||
120
providers/OpenAIResponses/GetResponseRequest.hpp
Normal file
120
providers/OpenAIResponses/GetResponseRequest.hpp
Normal file
@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright (C) 2024-2025 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <optional>
|
||||
|
||||
namespace QodeAssist::OpenAIResponses {
|
||||
|
||||
struct GetResponseRequest
|
||||
{
|
||||
QString responseId;
|
||||
std::optional<QStringList> include;
|
||||
std::optional<bool> includeObfuscation;
|
||||
std::optional<int> startingAfter;
|
||||
std::optional<bool> stream;
|
||||
|
||||
QString buildUrl(const QString &baseUrl) const
|
||||
{
|
||||
QString url = QString("%1/v1/responses/%2").arg(baseUrl, responseId);
|
||||
QStringList queryParams;
|
||||
|
||||
if (include && !include->isEmpty()) {
|
||||
for (const auto &item : *include) {
|
||||
queryParams.append(QString("include=%1").arg(item));
|
||||
}
|
||||
}
|
||||
|
||||
if (includeObfuscation) {
|
||||
queryParams.append(
|
||||
QString("include_obfuscation=%1").arg(*includeObfuscation ? "true" : "false"));
|
||||
}
|
||||
|
||||
if (startingAfter) {
|
||||
queryParams.append(QString("starting_after=%1").arg(*startingAfter));
|
||||
}
|
||||
|
||||
if (stream) {
|
||||
queryParams.append(QString("stream=%1").arg(*stream ? "true" : "false"));
|
||||
}
|
||||
|
||||
if (!queryParams.isEmpty()) {
|
||||
url += "?" + queryParams.join("&");
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
bool isValid() const { return !responseId.isEmpty(); }
|
||||
};
|
||||
|
||||
class GetResponseRequestBuilder
|
||||
{
|
||||
public:
|
||||
GetResponseRequestBuilder &setResponseId(const QString &id)
|
||||
{
|
||||
m_request.responseId = id;
|
||||
return *this;
|
||||
}
|
||||
|
||||
GetResponseRequestBuilder &setInclude(const QStringList &include)
|
||||
{
|
||||
m_request.include = include;
|
||||
return *this;
|
||||
}
|
||||
|
||||
GetResponseRequestBuilder &addInclude(const QString &item)
|
||||
{
|
||||
if (!m_request.include) {
|
||||
m_request.include = QStringList();
|
||||
}
|
||||
m_request.include->append(item);
|
||||
return *this;
|
||||
}
|
||||
|
||||
GetResponseRequestBuilder &setIncludeObfuscation(bool enabled)
|
||||
{
|
||||
m_request.includeObfuscation = enabled;
|
||||
return *this;
|
||||
}
|
||||
|
||||
GetResponseRequestBuilder &setStartingAfter(int sequence)
|
||||
{
|
||||
m_request.startingAfter = sequence;
|
||||
return *this;
|
||||
}
|
||||
|
||||
GetResponseRequestBuilder &setStream(bool enabled)
|
||||
{
|
||||
m_request.stream = enabled;
|
||||
return *this;
|
||||
}
|
||||
|
||||
GetResponseRequest build() const { return m_request; }
|
||||
|
||||
private:
|
||||
GetResponseRequest m_request;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::OpenAIResponses
|
||||
|
||||
219
providers/OpenAIResponses/InputTokensRequest.hpp
Normal file
219
providers/OpenAIResponses/InputTokensRequest.hpp
Normal file
@ -0,0 +1,219 @@
|
||||
/*
|
||||
* Copyright (C) 2024-2025 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ModelRequest.hpp"
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QString>
|
||||
|
||||
namespace QodeAssist::OpenAIResponses {
|
||||
|
||||
struct InputTokensRequest
|
||||
{
|
||||
std::optional<QString> conversation;
|
||||
std::optional<QJsonArray> input;
|
||||
std::optional<QString> instructions;
|
||||
std::optional<QString> model;
|
||||
std::optional<bool> parallelToolCalls;
|
||||
std::optional<QString> previousResponseId;
|
||||
std::optional<QJsonObject> reasoning;
|
||||
std::optional<QJsonObject> text;
|
||||
std::optional<QJsonValue> toolChoice;
|
||||
std::optional<QJsonArray> tools;
|
||||
std::optional<QString> truncation;
|
||||
|
||||
QString buildUrl(const QString &baseUrl) const
|
||||
{
|
||||
return QString("%1/v1/responses/input_tokens").arg(baseUrl);
|
||||
}
|
||||
|
||||
QJsonObject toJson() const
|
||||
{
|
||||
QJsonObject obj;
|
||||
|
||||
if (conversation)
|
||||
obj["conversation"] = *conversation;
|
||||
if (input)
|
||||
obj["input"] = *input;
|
||||
if (instructions)
|
||||
obj["instructions"] = *instructions;
|
||||
if (model)
|
||||
obj["model"] = *model;
|
||||
if (parallelToolCalls)
|
||||
obj["parallel_tool_calls"] = *parallelToolCalls;
|
||||
if (previousResponseId)
|
||||
obj["previous_response_id"] = *previousResponseId;
|
||||
if (reasoning)
|
||||
obj["reasoning"] = *reasoning;
|
||||
if (text)
|
||||
obj["text"] = *text;
|
||||
if (toolChoice)
|
||||
obj["tool_choice"] = *toolChoice;
|
||||
if (tools)
|
||||
obj["tools"] = *tools;
|
||||
if (truncation)
|
||||
obj["truncation"] = *truncation;
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
bool isValid() const { return input.has_value() || previousResponseId.has_value(); }
|
||||
};
|
||||
|
||||
class InputTokensRequestBuilder
|
||||
{
|
||||
public:
|
||||
InputTokensRequestBuilder &setConversation(const QString &conversationId)
|
||||
{
|
||||
m_request.conversation = conversationId;
|
||||
return *this;
|
||||
}
|
||||
|
||||
InputTokensRequestBuilder &setInput(const QJsonArray &input)
|
||||
{
|
||||
m_request.input = input;
|
||||
return *this;
|
||||
}
|
||||
|
||||
InputTokensRequestBuilder &addInputMessage(const Message &message)
|
||||
{
|
||||
if (!m_request.input) {
|
||||
m_request.input = QJsonArray();
|
||||
}
|
||||
m_request.input->append(message.toJson());
|
||||
return *this;
|
||||
}
|
||||
|
||||
InputTokensRequestBuilder &setInstructions(const QString &instructions)
|
||||
{
|
||||
m_request.instructions = instructions;
|
||||
return *this;
|
||||
}
|
||||
|
||||
InputTokensRequestBuilder &setModel(const QString &model)
|
||||
{
|
||||
m_request.model = model;
|
||||
return *this;
|
||||
}
|
||||
|
||||
InputTokensRequestBuilder &setParallelToolCalls(bool enabled)
|
||||
{
|
||||
m_request.parallelToolCalls = enabled;
|
||||
return *this;
|
||||
}
|
||||
|
||||
InputTokensRequestBuilder &setPreviousResponseId(const QString &responseId)
|
||||
{
|
||||
m_request.previousResponseId = responseId;
|
||||
return *this;
|
||||
}
|
||||
|
||||
InputTokensRequestBuilder &setReasoning(const QJsonObject &reasoning)
|
||||
{
|
||||
m_request.reasoning = reasoning;
|
||||
return *this;
|
||||
}
|
||||
|
||||
InputTokensRequestBuilder &setReasoningEffort(ReasoningEffort effort)
|
||||
{
|
||||
QString effortStr;
|
||||
switch (effort) {
|
||||
case ReasoningEffort::None:
|
||||
effortStr = "none";
|
||||
break;
|
||||
case ReasoningEffort::Minimal:
|
||||
effortStr = "minimal";
|
||||
break;
|
||||
case ReasoningEffort::Low:
|
||||
effortStr = "low";
|
||||
break;
|
||||
case ReasoningEffort::Medium:
|
||||
effortStr = "medium";
|
||||
break;
|
||||
case ReasoningEffort::High:
|
||||
effortStr = "high";
|
||||
break;
|
||||
}
|
||||
m_request.reasoning = QJsonObject{{"effort", effortStr}};
|
||||
return *this;
|
||||
}
|
||||
|
||||
InputTokensRequestBuilder &setText(const QJsonObject &text)
|
||||
{
|
||||
m_request.text = text;
|
||||
return *this;
|
||||
}
|
||||
|
||||
InputTokensRequestBuilder &setTextFormat(const TextFormatOptions &format)
|
||||
{
|
||||
m_request.text = format.toJson();
|
||||
return *this;
|
||||
}
|
||||
|
||||
InputTokensRequestBuilder &setToolChoice(const QJsonValue &toolChoice)
|
||||
{
|
||||
m_request.toolChoice = toolChoice;
|
||||
return *this;
|
||||
}
|
||||
|
||||
InputTokensRequestBuilder &setTools(const QJsonArray &tools)
|
||||
{
|
||||
m_request.tools = tools;
|
||||
return *this;
|
||||
}
|
||||
|
||||
InputTokensRequestBuilder &addTool(const Tool &tool)
|
||||
{
|
||||
if (!m_request.tools) {
|
||||
m_request.tools = QJsonArray();
|
||||
}
|
||||
m_request.tools->append(tool.toJson());
|
||||
return *this;
|
||||
}
|
||||
|
||||
InputTokensRequestBuilder &setTruncation(const QString &truncation)
|
||||
{
|
||||
m_request.truncation = truncation;
|
||||
return *this;
|
||||
}
|
||||
|
||||
InputTokensRequest build() const { return m_request; }
|
||||
|
||||
private:
|
||||
InputTokensRequest m_request;
|
||||
};
|
||||
|
||||
struct InputTokensResponse
|
||||
{
|
||||
QString object;
|
||||
int inputTokens = 0;
|
||||
|
||||
static InputTokensResponse fromJson(const QJsonObject &obj)
|
||||
{
|
||||
InputTokensResponse result;
|
||||
result.object = obj["object"].toString();
|
||||
result.inputTokens = obj["input_tokens"].toInt();
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::OpenAIResponses
|
||||
|
||||
143
providers/OpenAIResponses/ItemTypesReference.hpp
Normal file
143
providers/OpenAIResponses/ItemTypesReference.hpp
Normal file
@ -0,0 +1,143 @@
|
||||
/*
|
||||
* Copyright (C) 2024-2025 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace QodeAssist::OpenAIResponses {
|
||||
|
||||
/*
|
||||
* REFERENCE: Item Types in List Input Items Response
|
||||
* ===================================================
|
||||
*
|
||||
* The `data` array in ListInputItemsResponse can contain various item types.
|
||||
* This file serves as a reference for all possible item types.
|
||||
*
|
||||
* EXISTING TYPES (already implemented):
|
||||
* -------------------------------------
|
||||
* - MessageOutput (in ResponseObject.hpp)
|
||||
* - FunctionCall (in ResponseObject.hpp)
|
||||
* - ReasoningOutput (in ResponseObject.hpp)
|
||||
* - FileSearchCall (in ResponseObject.hpp)
|
||||
* - CodeInterpreterCall (in ResponseObject.hpp)
|
||||
* - Message (in ModelRequest.hpp) - for input messages
|
||||
*
|
||||
* ADDITIONAL TYPES (to be implemented if needed):
|
||||
* -----------------------------------------------
|
||||
*
|
||||
* 1. Computer Tool Call (computer_call)
|
||||
* - Computer use tool for UI automation
|
||||
* - Properties: action, call_id, id, pending_safety_checks, status, type
|
||||
* - Actions: click, double_click, drag, keypress, move, screenshot, scroll, type, wait
|
||||
*
|
||||
* 2. Computer Tool Call Output (computer_call_output)
|
||||
* - Output from computer tool
|
||||
* - Properties: call_id, id, output, type, acknowledged_safety_checks, status
|
||||
*
|
||||
* 3. Web Search Tool Call (web_search_call)
|
||||
* - Web search results
|
||||
* - Properties: action, id, status, type
|
||||
* - Actions: search, open_page, find
|
||||
*
|
||||
* 4. Image Generation Call (image_generation_call)
|
||||
* - AI image generation request
|
||||
* - Properties: id, result (base64), status, type
|
||||
*
|
||||
* 5. Local Shell Call (local_shell_call)
|
||||
* - Execute shell commands locally
|
||||
* - Properties: action (exec), call_id, id, status, type
|
||||
* - Action properties: command, env, timeout_ms, user, working_directory
|
||||
*
|
||||
* 6. Local Shell Call Output (local_shell_call_output)
|
||||
* - Output from local shell execution
|
||||
* - Properties: id, output (JSON string), type, status
|
||||
*
|
||||
* 7. Shell Tool Call (shell_call)
|
||||
* - Managed shell environment execution
|
||||
* - Properties: action, call_id, id, status, type, created_by
|
||||
*
|
||||
* 8. Shell Call Output (shell_call_output)
|
||||
* - Output from shell tool
|
||||
* - Properties: call_id, id, max_output_length, output (array), type, created_by
|
||||
* - Output chunks: outcome (exit/timeout), stderr, stdout
|
||||
*
|
||||
* 9. Apply Patch Tool Call (apply_patch_call)
|
||||
* - File diff operations
|
||||
* - Properties: call_id, id, operation, status, type, created_by
|
||||
* - Operations: create_file, delete_file, update_file
|
||||
*
|
||||
* 10. Apply Patch Tool Call Output (apply_patch_call_output)
|
||||
* - Output from patch operations
|
||||
* - Properties: call_id, id, status, type, created_by, output
|
||||
*
|
||||
* 11. MCP List Tools (mcp_list_tools)
|
||||
* - List of tools from MCP server
|
||||
* - Properties: id, server_label, tools (array), type, error
|
||||
*
|
||||
* 12. MCP Approval Request (mcp_approval_request)
|
||||
* - Request for human approval
|
||||
* - Properties: arguments, id, name, server_label, type
|
||||
*
|
||||
* 13. MCP Approval Response (mcp_approval_response)
|
||||
* - Response to approval request
|
||||
* - Properties: approval_request_id, approve (bool), id, type, reason
|
||||
*
|
||||
* 14. MCP Tool Call (mcp_call)
|
||||
* - Tool invocation on MCP server
|
||||
* - Properties: arguments, id, name, server_label, type
|
||||
* - Optional: approval_request_id, error, output, status
|
||||
*
|
||||
* 15. Custom Tool Call (custom_tool_call)
|
||||
* - User-defined tool call
|
||||
* - Properties: call_id, input, name, type, id
|
||||
*
|
||||
* 16. Custom Tool Call Output (custom_tool_call_output)
|
||||
* - Output from custom tool
|
||||
* - Properties: call_id, output (string or array), type, id
|
||||
*
|
||||
* 17. Item Reference (item_reference)
|
||||
* - Internal reference to another item
|
||||
* - Properties: id, type
|
||||
*
|
||||
* USAGE:
|
||||
* ------
|
||||
* When parsing ListInputItemsResponse.data array:
|
||||
* 1. Check item["type"] field
|
||||
* 2. Use appropriate parser based on type
|
||||
* 3. For existing types, use ResponseObject.hpp or ModelRequest.hpp
|
||||
* 4. For additional types, implement parsers as needed
|
||||
*
|
||||
* EXAMPLE:
|
||||
* --------
|
||||
* for (const auto &itemValue : response.data) {
|
||||
* const QJsonObject itemObj = itemValue.toObject();
|
||||
* const QString type = itemObj["type"].toString();
|
||||
*
|
||||
* if (type == "message") {
|
||||
* // Use MessageOutput or Message
|
||||
* } else if (type == "function_call") {
|
||||
* // Use FunctionCall
|
||||
* } else if (type == "computer_call") {
|
||||
* // Implement ComputerCall parser
|
||||
* }
|
||||
* // ... handle other types
|
||||
* }
|
||||
*/
|
||||
|
||||
} // namespace QodeAssist::OpenAIResponses
|
||||
|
||||
166
providers/OpenAIResponses/ListInputItemsRequest.hpp
Normal file
166
providers/OpenAIResponses/ListInputItemsRequest.hpp
Normal file
@ -0,0 +1,166 @@
|
||||
/*
|
||||
* Copyright (C) 2024-2025 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <optional>
|
||||
|
||||
namespace QodeAssist::OpenAIResponses {
|
||||
|
||||
enum class SortOrder { Ascending, Descending };
|
||||
|
||||
struct ListInputItemsRequest
|
||||
{
|
||||
QString responseId;
|
||||
std::optional<QString> after;
|
||||
std::optional<QStringList> include;
|
||||
std::optional<int> limit;
|
||||
std::optional<SortOrder> order;
|
||||
|
||||
QString buildUrl(const QString &baseUrl) const
|
||||
{
|
||||
QString url = QString("%1/v1/responses/%2/input_items").arg(baseUrl, responseId);
|
||||
QStringList queryParams;
|
||||
|
||||
if (after) {
|
||||
queryParams.append(QString("after=%1").arg(*after));
|
||||
}
|
||||
|
||||
if (include && !include->isEmpty()) {
|
||||
for (const auto &item : *include) {
|
||||
queryParams.append(QString("include=%1").arg(item));
|
||||
}
|
||||
}
|
||||
|
||||
if (limit) {
|
||||
queryParams.append(QString("limit=%1").arg(*limit));
|
||||
}
|
||||
|
||||
if (order) {
|
||||
QString orderStr = (*order == SortOrder::Ascending) ? "asc" : "desc";
|
||||
queryParams.append(QString("order=%1").arg(orderStr));
|
||||
}
|
||||
|
||||
if (!queryParams.isEmpty()) {
|
||||
url += "?" + queryParams.join("&");
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
bool isValid() const
|
||||
{
|
||||
if (responseId.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (limit && (*limit < 1 || *limit > 100)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
class ListInputItemsRequestBuilder
|
||||
{
|
||||
public:
|
||||
ListInputItemsRequestBuilder &setResponseId(const QString &id)
|
||||
{
|
||||
m_request.responseId = id;
|
||||
return *this;
|
||||
}
|
||||
|
||||
ListInputItemsRequestBuilder &setAfter(const QString &itemId)
|
||||
{
|
||||
m_request.after = itemId;
|
||||
return *this;
|
||||
}
|
||||
|
||||
ListInputItemsRequestBuilder &setInclude(const QStringList &include)
|
||||
{
|
||||
m_request.include = include;
|
||||
return *this;
|
||||
}
|
||||
|
||||
ListInputItemsRequestBuilder &addInclude(const QString &item)
|
||||
{
|
||||
if (!m_request.include) {
|
||||
m_request.include = QStringList();
|
||||
}
|
||||
m_request.include->append(item);
|
||||
return *this;
|
||||
}
|
||||
|
||||
ListInputItemsRequestBuilder &setLimit(int limit)
|
||||
{
|
||||
m_request.limit = limit;
|
||||
return *this;
|
||||
}
|
||||
|
||||
ListInputItemsRequestBuilder &setOrder(SortOrder order)
|
||||
{
|
||||
m_request.order = order;
|
||||
return *this;
|
||||
}
|
||||
|
||||
ListInputItemsRequestBuilder &setAscendingOrder()
|
||||
{
|
||||
m_request.order = SortOrder::Ascending;
|
||||
return *this;
|
||||
}
|
||||
|
||||
ListInputItemsRequestBuilder &setDescendingOrder()
|
||||
{
|
||||
m_request.order = SortOrder::Descending;
|
||||
return *this;
|
||||
}
|
||||
|
||||
ListInputItemsRequest build() const { return m_request; }
|
||||
|
||||
private:
|
||||
ListInputItemsRequest m_request;
|
||||
};
|
||||
|
||||
struct ListInputItemsResponse
|
||||
{
|
||||
QJsonArray data;
|
||||
QString firstId;
|
||||
QString lastId;
|
||||
bool hasMore = false;
|
||||
QString object;
|
||||
|
||||
static ListInputItemsResponse fromJson(const QJsonObject &obj)
|
||||
{
|
||||
ListInputItemsResponse result;
|
||||
result.data = obj["data"].toArray();
|
||||
result.firstId = obj["first_id"].toString();
|
||||
result.lastId = obj["last_id"].toString();
|
||||
result.hasMore = obj["has_more"].toBool();
|
||||
result.object = obj["object"].toString();
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::OpenAIResponses
|
||||
|
||||
354
providers/OpenAIResponses/ModelRequest.hpp
Normal file
354
providers/OpenAIResponses/ModelRequest.hpp
Normal file
@ -0,0 +1,354 @@
|
||||
/*
|
||||
* Copyright (C) 2024-2025 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QString>
|
||||
#include <QVariant>
|
||||
#include <optional>
|
||||
#include <variant>
|
||||
|
||||
namespace QodeAssist::OpenAIResponses {
|
||||
|
||||
enum class Role { User, Assistant, System, Developer };
|
||||
|
||||
enum class MessageStatus { InProgress, Completed, Incomplete };
|
||||
|
||||
enum class ReasoningEffort { None, Minimal, Low, Medium, High };
|
||||
|
||||
enum class TextFormat { Text, JsonSchema, JsonObject };
|
||||
|
||||
struct InputText
|
||||
{
|
||||
QString text;
|
||||
|
||||
QJsonObject toJson() const
|
||||
{
|
||||
return QJsonObject{{"type", "input_text"}, {"text", text}};
|
||||
}
|
||||
|
||||
bool isValid() const noexcept { return !text.isEmpty(); }
|
||||
};
|
||||
|
||||
struct InputImage
|
||||
{
|
||||
std::optional<QString> fileId;
|
||||
std::optional<QString> imageUrl;
|
||||
QString detail = "auto";
|
||||
|
||||
QJsonObject toJson() const
|
||||
{
|
||||
QJsonObject obj{{"type", "input_image"}, {"detail", detail}};
|
||||
if (fileId)
|
||||
obj["file_id"] = *fileId;
|
||||
if (imageUrl)
|
||||
obj["image_url"] = *imageUrl;
|
||||
return obj;
|
||||
}
|
||||
|
||||
bool isValid() const noexcept { return fileId.has_value() || imageUrl.has_value(); }
|
||||
};
|
||||
|
||||
struct InputFile
|
||||
{
|
||||
std::optional<QString> fileId;
|
||||
std::optional<QString> fileUrl;
|
||||
std::optional<QString> fileData;
|
||||
std::optional<QString> filename;
|
||||
|
||||
QJsonObject toJson() const
|
||||
{
|
||||
QJsonObject obj{{"type", "input_file"}};
|
||||
if (fileId)
|
||||
obj["file_id"] = *fileId;
|
||||
if (fileUrl)
|
||||
obj["file_url"] = *fileUrl;
|
||||
if (fileData)
|
||||
obj["file_data"] = *fileData;
|
||||
if (filename)
|
||||
obj["filename"] = *filename;
|
||||
return obj;
|
||||
}
|
||||
|
||||
bool isValid() const noexcept
|
||||
{
|
||||
return fileId.has_value() || fileUrl.has_value() || fileData.has_value();
|
||||
}
|
||||
};
|
||||
|
||||
class MessageContent
|
||||
{
|
||||
public:
|
||||
MessageContent(QString text) : m_variant(std::move(text)) {}
|
||||
MessageContent(InputText text) : m_variant(std::move(text)) {}
|
||||
MessageContent(InputImage image) : m_variant(std::move(image)) {}
|
||||
MessageContent(InputFile file) : m_variant(std::move(file)) {}
|
||||
|
||||
QJsonValue toJson() const
|
||||
{
|
||||
return std::visit([](const auto &content) -> QJsonValue {
|
||||
using T = std::decay_t<decltype(content)>;
|
||||
if constexpr (std::is_same_v<T, QString>) {
|
||||
return content;
|
||||
} else {
|
||||
return content.toJson();
|
||||
}
|
||||
}, m_variant);
|
||||
}
|
||||
|
||||
bool isValid() const noexcept
|
||||
{
|
||||
return std::visit([](const auto &content) -> bool {
|
||||
using T = std::decay_t<decltype(content)>;
|
||||
if constexpr (std::is_same_v<T, QString>) {
|
||||
return !content.isEmpty();
|
||||
} else {
|
||||
return content.isValid();
|
||||
}
|
||||
}, m_variant);
|
||||
}
|
||||
|
||||
private:
|
||||
std::variant<QString, InputText, InputImage, InputFile> m_variant;
|
||||
};
|
||||
|
||||
struct Message
|
||||
{
|
||||
Role role;
|
||||
QList<MessageContent> content;
|
||||
std::optional<MessageStatus> status;
|
||||
|
||||
QJsonObject toJson() const
|
||||
{
|
||||
QJsonObject obj;
|
||||
obj["role"] = roleToString(role);
|
||||
|
||||
if (content.size() == 1) {
|
||||
obj["content"] = content[0].toJson();
|
||||
} else {
|
||||
QJsonArray arr;
|
||||
for (const auto &c : content) {
|
||||
arr.append(c.toJson());
|
||||
}
|
||||
obj["content"] = arr;
|
||||
}
|
||||
|
||||
if (status) {
|
||||
obj["status"] = statusToString(*status);
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
bool isValid() const noexcept
|
||||
{
|
||||
if (content.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto &c : content) {
|
||||
if (!c.isValid()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static QString roleToString(Role r) noexcept
|
||||
{
|
||||
switch (r) {
|
||||
case Role::User:
|
||||
return "user";
|
||||
case Role::Assistant:
|
||||
return "assistant";
|
||||
case Role::System:
|
||||
return "system";
|
||||
case Role::Developer:
|
||||
return "developer";
|
||||
}
|
||||
return "user";
|
||||
}
|
||||
|
||||
static QString statusToString(MessageStatus s) noexcept
|
||||
{
|
||||
switch (s) {
|
||||
case MessageStatus::InProgress:
|
||||
return "in_progress";
|
||||
case MessageStatus::Completed:
|
||||
return "completed";
|
||||
case MessageStatus::Incomplete:
|
||||
return "incomplete";
|
||||
}
|
||||
return "in_progress";
|
||||
}
|
||||
};
|
||||
|
||||
struct FunctionTool
|
||||
{
|
||||
QString name;
|
||||
QJsonObject parameters;
|
||||
std::optional<QString> description;
|
||||
bool strict = true;
|
||||
|
||||
QJsonObject toJson() const
|
||||
{
|
||||
QJsonObject obj{{"type", "function"},
|
||||
{"name", name},
|
||||
{"parameters", parameters},
|
||||
{"strict", strict}};
|
||||
if (description)
|
||||
obj["description"] = *description;
|
||||
return obj;
|
||||
}
|
||||
|
||||
bool isValid() const noexcept
|
||||
{
|
||||
return !name.isEmpty() && !parameters.isEmpty();
|
||||
}
|
||||
};
|
||||
|
||||
struct FileSearchTool
|
||||
{
|
||||
QStringList vectorStoreIds;
|
||||
std::optional<int> maxNumResults;
|
||||
std::optional<double> scoreThreshold;
|
||||
|
||||
QJsonObject toJson() const
|
||||
{
|
||||
QJsonObject obj{{"type", "file_search"}};
|
||||
QJsonArray ids;
|
||||
for (const auto &id : vectorStoreIds) {
|
||||
ids.append(id);
|
||||
}
|
||||
obj["vector_store_ids"] = ids;
|
||||
|
||||
if (maxNumResults)
|
||||
obj["max_num_results"] = *maxNumResults;
|
||||
if (scoreThreshold)
|
||||
obj["score_threshold"] = *scoreThreshold;
|
||||
return obj;
|
||||
}
|
||||
|
||||
bool isValid() const noexcept
|
||||
{
|
||||
return !vectorStoreIds.isEmpty();
|
||||
}
|
||||
};
|
||||
|
||||
struct WebSearchTool
|
||||
{
|
||||
QString searchContextSize = "medium";
|
||||
|
||||
QJsonObject toJson() const
|
||||
{
|
||||
return QJsonObject{{"type", "web_search"}, {"search_context_size", searchContextSize}};
|
||||
}
|
||||
|
||||
bool isValid() const noexcept
|
||||
{
|
||||
return !searchContextSize.isEmpty();
|
||||
}
|
||||
};
|
||||
|
||||
struct CodeInterpreterTool
|
||||
{
|
||||
QString container;
|
||||
|
||||
QJsonObject toJson() const
|
||||
{
|
||||
return QJsonObject{{"type", "code_interpreter"}, {"container", container}};
|
||||
}
|
||||
|
||||
bool isValid() const noexcept
|
||||
{
|
||||
return !container.isEmpty();
|
||||
}
|
||||
};
|
||||
|
||||
class Tool
|
||||
{
|
||||
public:
|
||||
Tool(FunctionTool tool) : m_variant(std::move(tool)) {}
|
||||
Tool(FileSearchTool tool) : m_variant(std::move(tool)) {}
|
||||
Tool(WebSearchTool tool) : m_variant(std::move(tool)) {}
|
||||
Tool(CodeInterpreterTool tool) : m_variant(std::move(tool)) {}
|
||||
|
||||
QJsonObject toJson() const
|
||||
{
|
||||
return std::visit([](const auto &t) { return t.toJson(); }, m_variant);
|
||||
}
|
||||
|
||||
bool isValid() const noexcept
|
||||
{
|
||||
return std::visit([](const auto &t) { return t.isValid(); }, m_variant);
|
||||
}
|
||||
|
||||
private:
|
||||
std::variant<FunctionTool, FileSearchTool, WebSearchTool, CodeInterpreterTool> m_variant;
|
||||
};
|
||||
|
||||
struct TextFormatOptions
|
||||
{
|
||||
TextFormat type = TextFormat::Text;
|
||||
std::optional<QString> name;
|
||||
std::optional<QJsonObject> schema;
|
||||
std::optional<QString> description;
|
||||
std::optional<bool> strict;
|
||||
|
||||
QJsonObject toJson() const
|
||||
{
|
||||
QJsonObject obj;
|
||||
|
||||
switch (type) {
|
||||
case TextFormat::Text:
|
||||
obj["type"] = "text";
|
||||
break;
|
||||
case TextFormat::JsonSchema:
|
||||
obj["type"] = "json_schema";
|
||||
if (name)
|
||||
obj["name"] = *name;
|
||||
if (schema)
|
||||
obj["schema"] = *schema;
|
||||
if (description)
|
||||
obj["description"] = *description;
|
||||
if (strict)
|
||||
obj["strict"] = *strict;
|
||||
break;
|
||||
case TextFormat::JsonObject:
|
||||
obj["type"] = "json_object";
|
||||
break;
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
bool isValid() const noexcept
|
||||
{
|
||||
if (type == TextFormat::JsonSchema) {
|
||||
return name.has_value() && schema.has_value();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::OpenAIResponses
|
||||
|
||||
562
providers/OpenAIResponses/ResponseObject.hpp
Normal file
562
providers/OpenAIResponses/ResponseObject.hpp
Normal file
@ -0,0 +1,562 @@
|
||||
/*
|
||||
* Copyright (C) 2024-2025 Petr Mironychev
|
||||
*
|
||||
* This file is part of QodeAssist.
|
||||
*
|
||||
* QodeAssist is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QodeAssist is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <variant>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
|
||||
namespace QodeAssist::OpenAIResponses {
|
||||
|
||||
enum class ResponseStatus { Completed, Failed, InProgress, Cancelled, Queued, Incomplete };
|
||||
|
||||
enum class ItemStatus { InProgress, Completed, Incomplete };
|
||||
|
||||
struct FileCitation
|
||||
{
|
||||
QString fileId;
|
||||
QString filename;
|
||||
int index = 0;
|
||||
|
||||
static FileCitation fromJson(const QJsonObject &obj)
|
||||
{
|
||||
return {obj["file_id"].toString(), obj["filename"].toString(), obj["index"].toInt()};
|
||||
}
|
||||
|
||||
bool isValid() const noexcept { return !fileId.isEmpty(); }
|
||||
};
|
||||
|
||||
struct UrlCitation
|
||||
{
|
||||
QString url;
|
||||
QString title;
|
||||
int startIndex = 0;
|
||||
int endIndex = 0;
|
||||
|
||||
static UrlCitation fromJson(const QJsonObject &obj)
|
||||
{
|
||||
return {
|
||||
obj["url"].toString(),
|
||||
obj["title"].toString(),
|
||||
obj["start_index"].toInt(),
|
||||
obj["end_index"].toInt()};
|
||||
}
|
||||
|
||||
bool isValid() const noexcept { return !url.isEmpty(); }
|
||||
};
|
||||
|
||||
struct OutputText
|
||||
{
|
||||
QString text;
|
||||
QList<FileCitation> fileCitations;
|
||||
QList<UrlCitation> urlCitations;
|
||||
|
||||
static OutputText fromJson(const QJsonObject &obj)
|
||||
{
|
||||
OutputText result;
|
||||
result.text = obj["text"].toString();
|
||||
|
||||
if (obj.contains("annotations")) {
|
||||
const QJsonArray annotations = obj["annotations"].toArray();
|
||||
result.fileCitations.reserve(annotations.size());
|
||||
result.urlCitations.reserve(annotations.size());
|
||||
|
||||
for (const auto &annValue : annotations) {
|
||||
const QJsonObject ann = annValue.toObject();
|
||||
const QString type = ann["type"].toString();
|
||||
if (type == "file_citation") {
|
||||
result.fileCitations.append(FileCitation::fromJson(ann));
|
||||
} else if (type == "url_citation") {
|
||||
result.urlCitations.append(UrlCitation::fromJson(ann));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool isValid() const noexcept { return !text.isEmpty(); }
|
||||
};
|
||||
|
||||
struct Refusal
|
||||
{
|
||||
QString refusal;
|
||||
|
||||
static Refusal fromJson(const QJsonObject &obj)
|
||||
{
|
||||
return {obj["refusal"].toString()};
|
||||
}
|
||||
|
||||
bool isValid() const noexcept { return !refusal.isEmpty(); }
|
||||
};
|
||||
|
||||
struct MessageOutput
|
||||
{
|
||||
QString id;
|
||||
QString role;
|
||||
ItemStatus status = ItemStatus::InProgress;
|
||||
QList<OutputText> outputTexts;
|
||||
QList<Refusal> refusals;
|
||||
|
||||
static MessageOutput fromJson(const QJsonObject &obj)
|
||||
{
|
||||
MessageOutput result;
|
||||
result.id = obj["id"].toString();
|
||||
result.role = obj["role"].toString();
|
||||
|
||||
const QString statusStr = obj["status"].toString();
|
||||
if (statusStr == "in_progress")
|
||||
result.status = ItemStatus::InProgress;
|
||||
else if (statusStr == "completed")
|
||||
result.status = ItemStatus::Completed;
|
||||
else
|
||||
result.status = ItemStatus::Incomplete;
|
||||
|
||||
if (obj.contains("content")) {
|
||||
const QJsonArray content = obj["content"].toArray();
|
||||
result.outputTexts.reserve(content.size());
|
||||
result.refusals.reserve(content.size());
|
||||
|
||||
for (const auto &item : content) {
|
||||
const QJsonObject itemObj = item.toObject();
|
||||
const QString type = itemObj["type"].toString();
|
||||
|
||||
if (type == "output_text") {
|
||||
result.outputTexts.append(OutputText::fromJson(itemObj));
|
||||
} else if (type == "refusal") {
|
||||
result.refusals.append(Refusal::fromJson(itemObj));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool isValid() const noexcept { return !id.isEmpty(); }
|
||||
bool hasContent() const noexcept { return !outputTexts.isEmpty() || !refusals.isEmpty(); }
|
||||
};
|
||||
|
||||
struct FunctionCall
|
||||
{
|
||||
QString id;
|
||||
QString callId;
|
||||
QString name;
|
||||
QString arguments;
|
||||
ItemStatus status = ItemStatus::InProgress;
|
||||
|
||||
static FunctionCall fromJson(const QJsonObject &obj)
|
||||
{
|
||||
FunctionCall result;
|
||||
result.id = obj["id"].toString();
|
||||
result.callId = obj["call_id"].toString();
|
||||
result.name = obj["name"].toString();
|
||||
result.arguments = obj["arguments"].toString();
|
||||
|
||||
const QString statusStr = obj["status"].toString();
|
||||
if (statusStr == "in_progress")
|
||||
result.status = ItemStatus::InProgress;
|
||||
else if (statusStr == "completed")
|
||||
result.status = ItemStatus::Completed;
|
||||
else
|
||||
result.status = ItemStatus::Incomplete;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool isValid() const noexcept { return !id.isEmpty() && !callId.isEmpty() && !name.isEmpty(); }
|
||||
};
|
||||
|
||||
struct ReasoningOutput
|
||||
{
|
||||
QString id;
|
||||
ItemStatus status = ItemStatus::InProgress;
|
||||
QString summaryText;
|
||||
QString encryptedContent;
|
||||
QList<QString> contentTexts;
|
||||
|
||||
static ReasoningOutput fromJson(const QJsonObject &obj)
|
||||
{
|
||||
ReasoningOutput result;
|
||||
result.id = obj["id"].toString();
|
||||
|
||||
const QString statusStr = obj["status"].toString();
|
||||
if (statusStr == "in_progress")
|
||||
result.status = ItemStatus::InProgress;
|
||||
else if (statusStr == "completed")
|
||||
result.status = ItemStatus::Completed;
|
||||
else
|
||||
result.status = ItemStatus::Incomplete;
|
||||
|
||||
if (obj.contains("summary")) {
|
||||
const QJsonArray summary = obj["summary"].toArray();
|
||||
for (const auto &item : summary) {
|
||||
const QJsonObject itemObj = item.toObject();
|
||||
if (itemObj["type"].toString() == "summary_text") {
|
||||
result.summaryText = itemObj["text"].toString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (obj.contains("content")) {
|
||||
const QJsonArray content = obj["content"].toArray();
|
||||
result.contentTexts.reserve(content.size());
|
||||
|
||||
for (const auto &item : content) {
|
||||
const QJsonObject itemObj = item.toObject();
|
||||
if (itemObj["type"].toString() == "reasoning_text") {
|
||||
result.contentTexts.append(itemObj["text"].toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (obj.contains("encrypted_content")) {
|
||||
result.encryptedContent = obj["encrypted_content"].toString();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool isValid() const noexcept { return !id.isEmpty(); }
|
||||
bool hasContent() const noexcept
|
||||
{
|
||||
return !summaryText.isEmpty() || !contentTexts.isEmpty() || !encryptedContent.isEmpty();
|
||||
}
|
||||
};
|
||||
|
||||
struct FileSearchResult
|
||||
{
|
||||
QString fileId;
|
||||
QString filename;
|
||||
QString text;
|
||||
double score = 0.0;
|
||||
|
||||
static FileSearchResult fromJson(const QJsonObject &obj)
|
||||
{
|
||||
return {
|
||||
obj["file_id"].toString(),
|
||||
obj["filename"].toString(),
|
||||
obj["text"].toString(),
|
||||
obj["score"].toDouble()};
|
||||
}
|
||||
|
||||
bool isValid() const noexcept { return !fileId.isEmpty(); }
|
||||
};
|
||||
|
||||
struct FileSearchCall
|
||||
{
|
||||
QString id;
|
||||
QString status;
|
||||
QStringList queries;
|
||||
QList<FileSearchResult> results;
|
||||
|
||||
static FileSearchCall fromJson(const QJsonObject &obj)
|
||||
{
|
||||
FileSearchCall result;
|
||||
result.id = obj["id"].toString();
|
||||
result.status = obj["status"].toString();
|
||||
|
||||
if (obj.contains("queries")) {
|
||||
const QJsonArray queries = obj["queries"].toArray();
|
||||
result.queries.reserve(queries.size());
|
||||
|
||||
for (const auto &q : queries) {
|
||||
result.queries.append(q.toString());
|
||||
}
|
||||
}
|
||||
|
||||
if (obj.contains("results")) {
|
||||
const QJsonArray results = obj["results"].toArray();
|
||||
result.results.reserve(results.size());
|
||||
|
||||
for (const auto &r : results) {
|
||||
result.results.append(FileSearchResult::fromJson(r.toObject()));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool isValid() const noexcept { return !id.isEmpty(); }
|
||||
};
|
||||
|
||||
struct CodeInterpreterOutput
|
||||
{
|
||||
QString type;
|
||||
QString logs;
|
||||
QString imageUrl;
|
||||
|
||||
static CodeInterpreterOutput fromJson(const QJsonObject &obj)
|
||||
{
|
||||
CodeInterpreterOutput result;
|
||||
result.type = obj["type"].toString();
|
||||
if (result.type == "logs") {
|
||||
result.logs = obj["logs"].toString();
|
||||
} else if (result.type == "image") {
|
||||
result.imageUrl = obj["url"].toString();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool isValid() const noexcept
|
||||
{
|
||||
return !type.isEmpty() && (!logs.isEmpty() || !imageUrl.isEmpty());
|
||||
}
|
||||
};
|
||||
|
||||
struct CodeInterpreterCall
|
||||
{
|
||||
QString id;
|
||||
QString containerId;
|
||||
std::optional<QString> code;
|
||||
QString status;
|
||||
QList<CodeInterpreterOutput> outputs;
|
||||
|
||||
static CodeInterpreterCall fromJson(const QJsonObject &obj)
|
||||
{
|
||||
CodeInterpreterCall result;
|
||||
result.id = obj["id"].toString();
|
||||
result.containerId = obj["container_id"].toString();
|
||||
result.status = obj["status"].toString();
|
||||
|
||||
if (obj.contains("code") && !obj["code"].isNull()) {
|
||||
result.code = obj["code"].toString();
|
||||
}
|
||||
|
||||
if (obj.contains("outputs")) {
|
||||
const QJsonArray outputs = obj["outputs"].toArray();
|
||||
result.outputs.reserve(outputs.size());
|
||||
|
||||
for (const auto &o : outputs) {
|
||||
result.outputs.append(CodeInterpreterOutput::fromJson(o.toObject()));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool isValid() const noexcept { return !id.isEmpty() && !containerId.isEmpty(); }
|
||||
};
|
||||
|
||||
class OutputItem
|
||||
{
|
||||
public:
|
||||
enum class Type { Message, FunctionCall, Reasoning, FileSearch, CodeInterpreter, Unknown };
|
||||
|
||||
explicit OutputItem(const MessageOutput &msg)
|
||||
: m_type(Type::Message)
|
||||
, m_data(msg)
|
||||
{}
|
||||
explicit OutputItem(const FunctionCall &call)
|
||||
: m_type(Type::FunctionCall)
|
||||
, m_data(call)
|
||||
{}
|
||||
explicit OutputItem(const ReasoningOutput &reasoning)
|
||||
: m_type(Type::Reasoning)
|
||||
, m_data(reasoning)
|
||||
{}
|
||||
explicit OutputItem(const FileSearchCall &search)
|
||||
: m_type(Type::FileSearch)
|
||||
, m_data(search)
|
||||
{}
|
||||
explicit OutputItem(const CodeInterpreterCall &interpreter)
|
||||
: m_type(Type::CodeInterpreter)
|
||||
, m_data(interpreter)
|
||||
{}
|
||||
|
||||
Type type() const { return m_type; }
|
||||
|
||||
const MessageOutput *asMessage() const
|
||||
{
|
||||
return std::holds_alternative<MessageOutput>(m_data) ? &std::get<MessageOutput>(m_data)
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
const FunctionCall *asFunctionCall() const
|
||||
{
|
||||
return std::holds_alternative<FunctionCall>(m_data) ? &std::get<FunctionCall>(m_data)
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
const ReasoningOutput *asReasoning() const
|
||||
{
|
||||
return std::holds_alternative<ReasoningOutput>(m_data) ? &std::get<ReasoningOutput>(m_data)
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
const FileSearchCall *asFileSearch() const
|
||||
{
|
||||
return std::holds_alternative<FileSearchCall>(m_data) ? &std::get<FileSearchCall>(m_data)
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
const CodeInterpreterCall *asCodeInterpreter() const
|
||||
{
|
||||
return std::holds_alternative<CodeInterpreterCall>(m_data)
|
||||
? &std::get<CodeInterpreterCall>(m_data)
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
static OutputItem fromJson(const QJsonObject &obj)
|
||||
{
|
||||
const QString type = obj["type"].toString();
|
||||
|
||||
if (type == "message") {
|
||||
return OutputItem(MessageOutput::fromJson(obj));
|
||||
} else if (type == "function_call") {
|
||||
return OutputItem(FunctionCall::fromJson(obj));
|
||||
} else if (type == "reasoning") {
|
||||
return OutputItem(ReasoningOutput::fromJson(obj));
|
||||
} else if (type == "file_search_call") {
|
||||
return OutputItem(FileSearchCall::fromJson(obj));
|
||||
} else if (type == "code_interpreter_call") {
|
||||
return OutputItem(CodeInterpreterCall::fromJson(obj));
|
||||
}
|
||||
|
||||
return OutputItem(MessageOutput{});
|
||||
}
|
||||
|
||||
private:
|
||||
Type m_type;
|
||||
std::variant<MessageOutput, FunctionCall, ReasoningOutput, FileSearchCall, CodeInterpreterCall>
|
||||
m_data;
|
||||
};
|
||||
|
||||
struct Usage
|
||||
{
|
||||
int inputTokens = 0;
|
||||
int outputTokens = 0;
|
||||
int totalTokens = 0;
|
||||
|
||||
static Usage fromJson(const QJsonObject &obj)
|
||||
{
|
||||
return {
|
||||
obj["input_tokens"].toInt(),
|
||||
obj["output_tokens"].toInt(),
|
||||
obj["total_tokens"].toInt()
|
||||
};
|
||||
}
|
||||
|
||||
bool isValid() const noexcept { return totalTokens > 0; }
|
||||
};
|
||||
|
||||
struct ResponseError
|
||||
{
|
||||
QString code;
|
||||
QString message;
|
||||
|
||||
static ResponseError fromJson(const QJsonObject &obj)
|
||||
{
|
||||
return {obj["code"].toString(), obj["message"].toString()};
|
||||
}
|
||||
|
||||
bool isValid() const noexcept { return !code.isEmpty() && !message.isEmpty(); }
|
||||
};
|
||||
|
||||
struct Response
|
||||
{
|
||||
QString id;
|
||||
qint64 createdAt = 0;
|
||||
QString model;
|
||||
ResponseStatus status = ResponseStatus::InProgress;
|
||||
QList<OutputItem> output;
|
||||
QString outputText;
|
||||
std::optional<Usage> usage;
|
||||
std::optional<ResponseError> error;
|
||||
std::optional<QString> conversationId;
|
||||
|
||||
static Response fromJson(const QJsonObject &obj)
|
||||
{
|
||||
Response result;
|
||||
result.id = obj["id"].toString();
|
||||
result.createdAt = obj["created_at"].toInteger();
|
||||
result.model = obj["model"].toString();
|
||||
|
||||
const QString statusStr = obj["status"].toString();
|
||||
if (statusStr == "completed")
|
||||
result.status = ResponseStatus::Completed;
|
||||
else if (statusStr == "failed")
|
||||
result.status = ResponseStatus::Failed;
|
||||
else if (statusStr == "in_progress")
|
||||
result.status = ResponseStatus::InProgress;
|
||||
else if (statusStr == "cancelled")
|
||||
result.status = ResponseStatus::Cancelled;
|
||||
else if (statusStr == "queued")
|
||||
result.status = ResponseStatus::Queued;
|
||||
else
|
||||
result.status = ResponseStatus::Incomplete;
|
||||
|
||||
if (obj.contains("output")) {
|
||||
const QJsonArray output = obj["output"].toArray();
|
||||
result.output.reserve(output.size());
|
||||
|
||||
for (const auto &item : output) {
|
||||
result.output.append(OutputItem::fromJson(item.toObject()));
|
||||
}
|
||||
}
|
||||
|
||||
if (obj.contains("output_text")) {
|
||||
result.outputText = obj["output_text"].toString();
|
||||
}
|
||||
|
||||
if (obj.contains("usage")) {
|
||||
result.usage = Usage::fromJson(obj["usage"].toObject());
|
||||
}
|
||||
|
||||
if (obj.contains("error")) {
|
||||
result.error = ResponseError::fromJson(obj["error"].toObject());
|
||||
}
|
||||
|
||||
if (obj.contains("conversation")) {
|
||||
const QJsonObject conv = obj["conversation"].toObject();
|
||||
result.conversationId = conv["id"].toString();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
QString getAggregatedText() const
|
||||
{
|
||||
if (!outputText.isEmpty()) {
|
||||
return outputText;
|
||||
}
|
||||
|
||||
QString aggregated;
|
||||
for (const auto &item : output) {
|
||||
if (const auto *msg = item.asMessage()) {
|
||||
for (const auto &text : msg->outputTexts) {
|
||||
aggregated += text.text;
|
||||
}
|
||||
}
|
||||
}
|
||||
return aggregated;
|
||||
}
|
||||
|
||||
bool isValid() const noexcept { return !id.isEmpty(); }
|
||||
bool hasError() const noexcept { return error.has_value(); }
|
||||
bool isCompleted() const noexcept { return status == ResponseStatus::Completed; }
|
||||
bool isFailed() const noexcept { return status == ResponseStatus::Failed; }
|
||||
};
|
||||
|
||||
} // namespace QodeAssist::OpenAIResponses
|
||||
|
||||
Reference in New Issue
Block a user