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%
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