SPB Git

spb/zyquo-agent Public MIT

The autonomous agent that actually operates your Mac — plans, runs real commands, verifies its own work.

Swift 94.7% Shell 4.1% Python 0.7% Makefile 0.5%
2.9 KB · 83 lines swift
Raw Blame History
1//2//  JSONValue.swift3//  Zyquo Agent4//5//  Author: Simon-Pierre Boucher6//  Mail: contact@spboucher.ai7//8//  A Codable representation of arbitrary JSON, used to embed tool parameter9//  JSON Schemas and tool_use inputs verbatim inside Encodable wire requests10//  (JSONEncoder cannot encode `Any`).11//1213import Foundation1415/// Arbitrary JSON, encodable/decodable losslessly.16enum JSONValue: Codable, Hashable {17    case null18    case bool(Bool)19    case number(Double)20    case string(String)21    case array([JSONValue])22    case object([String: JSONValue])2324    init(from decoder: Decoder) throws {25        let container = try decoder.singleValueContainer()26        if container.decodeNil() {27            self = .null28        } else if let b = try? container.decode(Bool.self) {29            self = .bool(b)30        } else if let n = try? container.decode(Double.self) {31            self = .number(n)32        } else if let s = try? container.decode(String.self) {33            self = .string(s)34        } else if let a = try? container.decode([JSONValue].self) {35            self = .array(a)36        } else if let o = try? container.decode([String: JSONValue].self) {37            self = .object(o)38        } else {39            throw DecodingError.dataCorruptedError(in: container, debugDescription: "unrecognized JSON value")40        }41    }4243    func encode(to encoder: Encoder) throws {44        var container = encoder.singleValueContainer()45        switch self {46        case .null: try container.encodeNil()47        case .bool(let b): try container.encode(b)48        case .number(let n):49            // Preserve integer-looking numbers without a trailing ".0".50            if n.truncatingRemainder(dividingBy: 1) == 0,51               n >= Double(Int64.min), n <= Double(Int64.max) {52                try container.encode(Int64(n))53            } else {54                try container.encode(n)55            }56        case .string(let s): try container.encode(s)57        case .array(let a): try container.encode(a)58        case .object(let o): try container.encode(o)59        }60    }6162    /// Parses a JSON string (e.g. a ToolSpec's parameter schema). Returns nil63    /// on malformed input.64    static func parse(_ jsonString: String) -> JSONValue? {65        guard let data = jsonString.data(using: .utf8) else { return nil }66        return try? JSONDecoder().decode(JSONValue.self, from: data)67    }6869    /// The canonical empty-object schema, used when a tool declares no parameters70    /// or a schema string fails to parse (providers reject absent schemas).71    static let emptyObject = JSONValue.object([:])7273    /// Compact JSON serialization (used to turn tool_use inputs back into the74    /// normalized ToolCall.argumentsJSON string).75    var jsonString: String {76        guard let data = try? JSONEncoder().encode(self),77              let string = String(data: data, encoding: .utf8) else {78            return "{}"79        }80        return string81    }82}83