// // JSONValue.swift // Zyquo Agent // // Author: Simon-Pierre Boucher // Mail: contact@spboucher.ai // // A Codable representation of arbitrary JSON, used to embed tool parameter // JSON Schemas and tool_use inputs verbatim inside Encodable wire requests // (JSONEncoder cannot encode `Any`). // import Foundation /// Arbitrary JSON, encodable/decodable losslessly. enum JSONValue: Codable, Hashable { case null case bool(Bool) case number(Double) case string(String) case array([JSONValue]) case object([String: JSONValue]) init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() if container.decodeNil() { self = .null } else if let b = try? container.decode(Bool.self) { self = .bool(b) } else if let n = try? container.decode(Double.self) { self = .number(n) } else if let s = try? container.decode(String.self) { self = .string(s) } else if let a = try? container.decode([JSONValue].self) { self = .array(a) } else if let o = try? container.decode([String: JSONValue].self) { self = .object(o) } else { throw DecodingError.dataCorruptedError(in: container, debugDescription: "unrecognized JSON value") } } func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() switch self { case .null: try container.encodeNil() case .bool(let b): try container.encode(b) case .number(let n): // Preserve integer-looking numbers without a trailing ".0". if n.truncatingRemainder(dividingBy: 1) == 0, n >= Double(Int64.min), n <= Double(Int64.max) { try container.encode(Int64(n)) } else { try container.encode(n) } case .string(let s): try container.encode(s) case .array(let a): try container.encode(a) case .object(let o): try container.encode(o) } } /// Parses a JSON string (e.g. a ToolSpec's parameter schema). Returns nil /// on malformed input. static func parse(_ jsonString: String) -> JSONValue? { guard let data = jsonString.data(using: .utf8) else { return nil } return try? JSONDecoder().decode(JSONValue.self, from: data) } /// The canonical empty-object schema, used when a tool declares no parameters /// or a schema string fails to parse (providers reject absent schemas). static let emptyObject = JSONValue.object([:]) /// Compact JSON serialization (used to turn tool_use inputs back into the /// normalized ToolCall.argumentsJSON string). var jsonString: String { guard let data = try? JSONEncoder().encode(self), let string = String(data: data, encoding: .utf8) else { return "{}" } return string } }