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%
4.8 KB · 126 lines swift
Raw Blame History
1//2//  ToolTypes.swift3//  Zyquo Agent4//5//  Author: Simon-Pierre Boucher6//  Mail: contact@spboucher.ai7//8//  The normalized tool-calling vocabulary shared by the agent loop, the tool9//  registry, and every provider client (docs/PROVIDER-REUSE.md §5.3). Clients10//  translate these to/from each wire dialect (OpenAI `tools`/`tool_calls`,11//  Anthropic `tools`/`tool_use`/`tool_result`) — nothing provider-specific12//  leaks above this layer.13//1415import Foundation1617/// A tool offered to the model, as handed to the client by the ToolRegistry.18struct ToolSpec: Codable, Hashable {19    var name: String20    var description: String21    /// Canonical JSON Schema (an `object` schema) for the tool's parameters,22    /// serialized as a JSON string. Clients embed it as raw JSON in requests.23    var parametersJSONSchema: String2425    init(name: String, description: String, parametersJSONSchema: String) {26        self.name = name27        self.description = description28        self.parametersJSONSchema = parametersJSONSchema29    }30}3132/// One tool invocation emitted by the model.33struct ToolCall: Codable, Identifiable, Hashable {34    /// Provider call ID (`toolu_…` / `call_…`); synthesized when a provider omits it.35    var id: String36    var name: String37    /// Raw accumulated JSON string of the arguments; parsed and validated38    /// against the ToolSpec schema by the agent loop before execution.39    var argumentsJSON: String40    /// Gemini 3+ thought signature (`extra_content.google.thought_signature`41    /// on the compat endpoint). Opaque; captured from responses and echoed42    /// back verbatim when the call is threaded into history — Gemini rejects43    /// tool results whose originating call lost its signature (verified live,44    /// Phase 7.1). Nil for every other provider.45    var thoughtSignature: String?4647    init(id: String, name: String, argumentsJSON: String, thoughtSignature: String? = nil) {48        self.id = id49        self.name = name50        self.argumentsJSON = argumentsJSON51        self.thoughtSignature = thoughtSignature52    }5354    /// Arguments parsed to a dictionary; nil when the model produced55    /// malformed JSON (the loop re-prompts on that).56    var argumentsDictionary: [String: Any]? {57        guard let data = argumentsJSON.data(using: .utf8),58              let obj = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {59            return nil60        }61        return obj62    }63}6465/// The outcome of executing one tool call, threaded back on the next request.66struct ToolResult: Codable, Hashable {67    var toolCallID: String68    /// Stringified output (stdout/stderr summary, file text, error message…).69    var content: String70    var isError: Bool7172    init(toolCallID: String, content: String, isError: Bool = false) {73        self.toolCallID = toolCallID74        self.content = content75        self.isError = isError76    }77}7879/// How the model is allowed to use the offered tools.80enum ToolChoice: Hashable {81    /// Model decides freely (the default; omitted from the wire when possible).82    case auto83    /// Tools are declared but must not be called.84    case none85    /// The model must call some tool (OpenAI "required" / Anthropic "any").86    case required87    /// The model must call this specific tool.88    case named(String)89}9091/// Normalized stop condition, derived from the provider's raw finish/stop92/// reason. The agent loop branches only on this — never on raw strings.93enum StopReason: Hashable {94    /// Natural end of the assistant turn ("stop", "end_turn", "eos", …).95    case endTurn96    /// The model is waiting for tool results ("tool_calls" / "tool_use").97    case toolUse98    /// Output truncated by the token limit ("length" / "max_tokens").99    case maxTokens100    /// The model refused ("refusal" / "content_filter").101    case refusal102    /// Anything else (provider-specific values like DeepSeek's103    /// "insufficient_system_resource"), with the raw string preserved.104    case other(String?)105106    /// Maps every observed finish_reason/stop_reason vocabulary (OpenAI schema107    /// and Anthropic Messages) to the normalized enum. `hasToolCalls` covers108    /// providers that stream tool calls but report a plain "stop".109    static func normalize(_ raw: String?, hasToolCalls: Bool = false) -> StopReason {110        switch raw {111        case "tool_calls", "tool_use", "function_call":112            return .toolUse113        case "stop", "end_turn", "eos", "stop_sequence", "pause_turn":114            return hasToolCalls ? .toolUse : .endTurn115        case "length", "max_tokens", "model_context_window_exceeded":116            return .maxTokens117        case "refusal", "content_filter":118            return .refusal119        case nil:120            return hasToolCalls ? .toolUse : .endTurn121        default:122            return hasToolCalls ? .toolUse : .other(raw)123        }124    }125}126