/* * 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 . */ #pragma once #include #include #include #include #include #include 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 fileCitations; QList 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 outputTexts; QList 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 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 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 code; QString status; QList 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(m_data) ? &std::get(m_data) : nullptr; } const FunctionCall *asFunctionCall() const { return std::holds_alternative(m_data) ? &std::get(m_data) : nullptr; } const ReasoningOutput *asReasoning() const { return std::holds_alternative(m_data) ? &std::get(m_data) : nullptr; } const FileSearchCall *asFileSearch() const { return std::holds_alternative(m_data) ? &std::get(m_data) : nullptr; } const CodeInterpreterCall *asCodeInterpreter() const { return std::holds_alternative(m_data) ? &std::get(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 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 output; QString outputText; std::optional usage; std::optional error; std::optional 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