Swift SDK

The Swift SDK provides a native, type-safe client for the Diminuendo gateway, purpose-built for macOS and iOS applications. It leverages Swift Concurrency (actors, async/await, AsyncStream) and Foundation’s URLSessionWebSocketTask for zero third-party dependencies.
Designed for macOS 26+ SwiftUI applications, but supports macOS 13+ and iOS 16+ for backward compatibility. Targets Swift 6.0 with strict concurrency checking — all public types are Sendable.

Installation

Add the SDK to your Swift Package Manager dependencies:
// Package.swift
dependencies: [
    .package(path: "../sdk/swift")  // Local reference
    // Or: .package(url: "https://github.com/iGentAI/diminuendo-swift", from: "0.1.0")
],
targets: [
    .target(name: "MyApp", dependencies: ["DiminuendoSDK"])
]

Quick Start

import DiminuendoSDK

let client = DiminuendoClient(options: .init(
    url: "ws://localhost:8080/ws",
    token: ""  // Empty for dev mode
))

// Connect and authenticate
try await client.connect()

// Listen for events in a background task
Task {
    for await event in await client.events {
        switch event {
        case .textDelta(_, _, let text, _, _):
            print(text, terminator: "")
        case .turnComplete(_, _, let finalText, _, _):
            print("\nDone: \(finalText)")
        case .error(let code, let message):
            print("Error [\(code)]: \(message)")
        default:
            break
        }
    }
}

// Create a session and run a turn
let session = try await client.createSession(agentType: "coding-agent", name: "My Session")
let snapshot = try await client.joinSession(sessionId: session.id)
try await client.runTurn(sessionId: session.id, text: "Write a hello world in Swift")

Architecture

The client is implemented as a Swift actor, providing inherent thread safety for all mutable state. The WebSocket connection runs on a background Task, delivering events via an AsyncStream<ServerEvent>.
┌──────────────────────────────────────────────┐
│              DiminuendoClient (actor)          │
│                                                │
│  ┌─────────────┐  ┌──────────────────────┐   │
│  │ send(msg)    │  │ URLSessionWebSocket  │   │
│  │ request(msg) │  │ Task (background)    │   │
│  └──────┬──────┘  └──────────┬───────────┘   │
│         │                     │               │
│         ▼                     ▼               │
│  ┌──────────────────────────────────────┐    │
│  │      AsyncStream<ServerEvent>        │    │
│  │      (continuation-based)            │    │
│  └──────────────────────────────────────┘    │
└──────────────────────────────────────────────┘

Configuration

public struct ClientOptions: Sendable {
    public let url: String              // Gateway WebSocket URL
    public let token: String            // Auth token (empty for dev mode)
    public let autoReconnect: Bool      // Default: true
    public let reconnectDelay: TimeInterval  // Default: 1.0s
    public let pingInterval: TimeInterval    // Default: 30.0s (0 to disable)
}

Connection Lifecycle

let client = DiminuendoClient(options: .init(url: "ws://localhost:8080/ws"))

// connect() waits for the authenticated event before returning
try await client.connect()

print(await client.isConnected)      // true
print(await client.isAuthenticated)  // true
print(await client.identity)         // Identity(userId:, email:, tenantId:)

// Disconnect gracefully
await client.disconnect()

Session Management

All session management methods are async and return typed results:
// List sessions
let sessions = try await client.listSessions(includeArchived: false)

// Create, rename, archive, unarchive, delete
let session = try await client.createSession(agentType: "coding-agent", name: "My Session")
let renamed = try await client.renameSession(sessionId: session.id, name: "Better Name")
let archived = try await client.archiveSession(sessionId: session.id)
let restored = try await client.unarchiveSession(sessionId: session.id)
try await client.deleteSession(sessionId: session.id)

Session Interaction

// Join a session (returns full state snapshot)
let snapshot = try await client.joinSession(sessionId: session.id)
// Optionally resume from a specific sequence number
let resumed = try await client.joinSession(sessionId: session.id, afterSeq: 42)

// Run a turn (fire-and-forget — events arrive via the stream)
try await client.runTurn(sessionId: session.id, text: "Explain this code")

// Mid-turn controls
try await client.stopTurn(sessionId: session.id)
try await client.steer(sessionId: session.id, content: "Focus on error handling")

// Answer agent questions
try await client.answerQuestion(
    sessionId: session.id,
    requestId: "req-123",
    answers: ["choice": "option-a"]
)

// Retrieve history and events
let history = try await client.getHistory(sessionId: session.id)
let events = try await client.getEvents(sessionId: session.id, afterSeq: 10, limit: 100)

// Ping for latency measurement
let latencyMs = try await client.ping()

File Access

let files = try await client.listFiles(sessionId: session.id, path: "/src", depth: 2)
let content = try await client.readFile(sessionId: session.id, path: "/src/main.swift")
let versions = try await client.fileHistory(sessionId: session.id, path: "/src/main.swift")
let oldVersion = try await client.fileAtIteration(sessionId: session.id, path: "/src/main.swift", iteration: 3)

Member Management

let result = try await client.manageMembers(action: "list")
try await client.manageMembers(action: "set_role", userId: "user-123", role: "admin")
try await client.manageMembers(action: "remove", userId: "user-456")

Event Handling

Events are delivered via an AsyncStream<ServerEvent>. The ServerEvent enum has 51+ variants covering the full protocol:
Task {
    for await event in await client.events {
        switch event {
        // Turn lifecycle
        case .turnStarted(let sid, let tid, _, _):
            print("Turn \(tid) started in \(sid)")
        case .textDelta(_, _, let text, _, _):
            print(text, terminator: "")
        case .turnComplete(_, _, let finalText, _, _):
            print("\n--- Complete: \(finalText.prefix(100))...")

        // Tool calls
        case .toolCall(_, _, let id, let name, let args, _, _):
            print("Tool: \(name) (\(id))")
        case .toolResult(_, _, let id, let status, let output, _, _):
            print("Result: \(status)\(output ?? "")")

        // Thinking
        case .thinkingStart(_, _, _, _):
            print("[thinking...]")
        case .thinkingProgress(_, _, let text, _, _):
            print("  \(text)", terminator: "")

        // Interactive
        case .questionRequested(_, let reqId, let questions, _):
            print("Question \(reqId): \(questions)")

        // Errors
        case .error(let code, let message):
            print("Error [\(code)]: \(message)")
        case .serverShutdown(let reason, _):
            print("Server shutting down: \(reason)")

        // Unknown future events
        case .unknown(let type, _):
            print("Unknown event: \(type)")

        default:
            break
        }
    }
}

Error Handling

public enum ClientError: Error, Sendable {
    case notConnected
    case connectionFailed(String)
    case authenticationFailed
    case authenticationTimeout
    case requestTimeout(String)
    case serializationError(String)
    case webSocketError(String)
}
do {
    try await client.connect()
    let sessions = try await client.listSessions()
} catch DiminuendoClient.ClientError.authenticationTimeout {
    print("Auth timed out — check token")
} catch DiminuendoClient.ClientError.requestTimeout(let msg) {
    print("Request timed out: \(msg)")
} catch DiminuendoClient.ClientError.notConnected {
    print("Not connected — call connect() first")
} catch {
    print("Unexpected: \(error)")
}

Types

All types conform to Codable, Sendable, and Equatable:
TypeDescription
SessionMetaSession metadata (id, status, agentType, timestamps)
SessionStatus7-state enum: inactive, activating, ready, running, waiting, deactivating, error
IdentityAuthenticated user (userId, email, tenantId)
StateSnapshotFull session state on join (session, currentTurn, history, sandbox)
FileEntryFile/directory in agent workspace
FileContentFile content with encoding and size
IterationMetaFile version metadata
HistoryItemConversation message
MemberRecordTenant member with role
AnyCodableType-erased JSON value wrapper
ClientMessage21-variant enum (Encodable)
ServerEvent51-variant enum (Decodable) with .unknown catch-all

Testing

193 tests covering serialization, deserialization, wire format, adversarial JSON, and edge cases:
cd sdk/swift && swift test
The Swift SDK uses Swift Testing framework (@Test, #expect). All 51 server event types are tested with exact wire JSON, and all 21 client messages verify correct camelCase serialization with optional field omission.