diff --git a/Sources/AIProxy/EachAI/EachAIWorkflowExecutionResponseBody.swift b/Sources/AIProxy/EachAI/EachAIWorkflowExecutionResponseBody.swift index 3e0118b..d53c48c 100644 --- a/Sources/AIProxy/EachAI/EachAIWorkflowExecutionResponseBody.swift +++ b/Sources/AIProxy/EachAI/EachAIWorkflowExecutionResponseBody.swift @@ -19,13 +19,14 @@ nonisolated public struct EachAIWorkflowExecutionResponseBody: Decodable, Sendab public let organizationId: String? public let output: String? public let outputJson: String? + public let parameters: [Parameter]? public let sourceIpAddress: String? public let startedAt: String? public let status: String? public let stepResults: [StepResult]? public let updatedAt: String? - - public init(averagePercent: Double?, createdAt: String?, deletedAt: String?, endedAt: String?, executionId: String?, flowId: String?, flowName: String?, organizationId: String?, output: String?, outputJson: String?, sourceIpAddress: String?, startedAt: String?, status: String?, stepResults: [StepResult]?, updatedAt: String?) { + + public init(averagePercent: Double?, createdAt: String?, deletedAt: String?, endedAt: String?, executionId: String?, flowId: String?, flowName: String?, organizationId: String?, output: String?, outputJson: String?, parameters: [Parameter]?, sourceIpAddress: String?, startedAt: String?, status: String?, stepResults: [StepResult]?, updatedAt: String?) { self.averagePercent = averagePercent self.createdAt = createdAt self.deletedAt = deletedAt @@ -36,6 +37,7 @@ nonisolated public struct EachAIWorkflowExecutionResponseBody: Decodable, Sendab self.organizationId = organizationId self.output = output self.outputJson = outputJson + self.parameters = parameters self.sourceIpAddress = sourceIpAddress self.startedAt = startedAt self.status = status @@ -43,6 +45,27 @@ nonisolated public struct EachAIWorkflowExecutionResponseBody: Decodable, Sendab self.updatedAt = updatedAt } + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + self.averagePercent = try container.decodeIfPresent(Double.self, forKey: .averagePercent) + self.createdAt = try container.decodeIfPresent(String.self, forKey: .createdAt) + self.deletedAt = try container.decodeIfPresent(String.self, forKey: .deletedAt) + self.endedAt = try container.decodeIfPresent(String.self, forKey: .endedAt) + self.executionId = try container.decodeIfPresent(String.self, forKey: .executionId) + self.flowId = try container.decodeIfPresent(String.self, forKey: .flowId) + self.flowName = try container.decodeIfPresent(String.self, forKey: .flowName) + self.organizationId = try container.decodeIfPresent(String.self, forKey: .organizationId) + self.output = try container.decodeIfPresent(String.self, forKey: .output) + self.outputJson = container.decodeStringOrArrayIfPresent(forKey: .outputJson) + self.parameters = try container.decodeIfPresent([Parameter].self, forKey: .parameters) + self.sourceIpAddress = try container.decodeIfPresent(String.self, forKey: .sourceIpAddress) + self.startedAt = try container.decodeIfPresent(String.self, forKey: .startedAt) + self.status = try container.decodeIfPresent(String.self, forKey: .status) + self.stepResults = try container.decodeIfPresent([StepResult].self, forKey: .stepResults) + self.updatedAt = try container.decodeIfPresent(String.self, forKey: .updatedAt) + } + private enum CodingKeys: String, CodingKey { case averagePercent = "average_percent" case createdAt = "created_at" @@ -54,6 +77,7 @@ nonisolated public struct EachAIWorkflowExecutionResponseBody: Decodable, Sendab case organizationId = "organization_id" case output case outputJson = "output_json" + case parameters case sourceIpAddress = "source_ip_address" case startedAt = "started_at" case status @@ -62,6 +86,19 @@ nonisolated public struct EachAIWorkflowExecutionResponseBody: Decodable, Sendab } } +// MARK: - ResponseBody.Parameter +extension EachAIWorkflowExecutionResponseBody { + nonisolated public struct Parameter: Decodable, Sendable { + public let name: String? + public let value: String? + + public init(name: String?, value: String?) { + self.name = name + self.value = value + } + } +} + // MARK: - ResponseBody.StepResult extension EachAIWorkflowExecutionResponseBody { nonisolated public struct StepResult: Decodable, Sendable { @@ -89,6 +126,21 @@ extension EachAIWorkflowExecutionResponseBody { self.version = version } + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + self.endedAt = try container.decodeIfPresent(String.self, forKey: .endedAt) + self.input = try container.decodeIfPresent(String.self, forKey: .input) + self.model = try container.decodeIfPresent(String.self, forKey: .model) + self.outputJson = container.decodeStringOrArrayIfPresent(forKey: .outputJson) + self.output = try container.decodeIfPresent(String.self, forKey: .output) + self.startedAt = try container.decodeIfPresent(String.self, forKey: .startedAt) + self.status = try container.decodeIfPresent(String.self, forKey: .status) + self.stepId = try container.decodeIfPresent(String.self, forKey: .stepId) + self.stepName = try container.decodeIfPresent(String.self, forKey: .stepName) + self.version = try container.decodeIfPresent(String.self, forKey: .version) + } + private enum CodingKeys: String, CodingKey { case endedAt = "ended_at" case input @@ -104,3 +156,20 @@ extension EachAIWorkflowExecutionResponseBody { } } +// MARK: - KeyedDecodingContainer Extension +extension KeyedDecodingContainer { + /// Decodes a value as either a String or [String], returning the first element if it's an array. + /// + /// This is needed because the EachAI API inconsistently returns `output_json` and `output` fields: + /// sometimes as a string, sometimes as an array of strings. To prevent decoding failures, + /// we attempt both formats and extract the first element when an array is returned. + func decodeStringOrArrayIfPresent(forKey key: Key) -> String? { + if let stringValue = try? decodeIfPresent(String.self, forKey: key) { + return stringValue + } else if let arrayValue = try? decodeIfPresent([String].self, forKey: key) { + return arrayValue.first + } else { + return nil + } + } +}