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:
| Type | Description |
|---|
SessionMeta | Session metadata (id, status, agentType, timestamps) |
SessionStatus | 7-state enum: inactive, activating, ready, running, waiting, deactivating, error |
Identity | Authenticated user (userId, email, tenantId) |
StateSnapshot | Full session state on join (session, currentTurn, history, sandbox) |
FileEntry | File/directory in agent workspace |
FileContent | File content with encoding and size |
IterationMeta | File version metadata |
HistoryItem | Conversation message |
MemberRecord | Tenant member with role |
AnyCodable | Type-erased JSON value wrapper |
ClientMessage | 21-variant enum (Encodable) |
ServerEvent | 51-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.