SDKs

Diminuendo ships four official client SDKs that cover the full surface of the wire protocol — all 21 client message types and all 51 server event types — with no exceptions, no partial implementations, and no event silently dropped. Each SDK is a standalone library with zero gateway dependencies: it defines its own types from the wire protocol specification, connects over a standard WebSocket, and can be published and consumed independently. The SDKs are not generated from a shared schema; they are handcrafted in each language’s native idiom, because a Rust developer should not have to think in TypeScript abstractions, and a Swift developer should not encounter Python’s naming conventions.

Design Principles

Full Protocol Parity

Every client message type and every server event type is represented in each SDK. No event is silently dropped. No message type is missing. The SDKs are functionally equivalent — anything one can do, all can do.

Zero Gateway Dependencies

The SDKs do not import gateway code, do not share types via monorepo packages, and do not require code generation. Each SDK defines its own types from the wire protocol specification. This ensures they can be published and used independently.

Native Idioms

TypeScript uses Promises and typed event handlers. Rust uses async channels and serde. Python uses asyncio and dataclasses. Swift uses async/await and Codable. Each SDK feels native to its language — no lowest-common-denominator abstraction.

Adversarial Test Suites

Every SDK has a comprehensive test suite that does not merely verify happy paths, but actively attempts to break the client with malformed data, concurrent operations, timeouts, and edge cases.

SDK Comparison

AspectTypeScriptRustPythonSwift
RuntimeNode.js, Bun, Deno, browserstokio (async runtime)asyncio + websocketsSwift Concurrency + URLSessionWebSocketTask
DependenciesZero runtime depstokio, serde, tokio-tungstenite, futures-util, thiserror, tracingwebsocketsNone (Foundation only)
Async ModelPromises + event callbacksmpsc::UnboundedReceiver<ServerEvent>async/await + event callbacksasync/await + AsyncStream<ServerEvent>
Type SystemTypeScript interfaces (compile-time)Rust enums with #[serde(tag = "type")] (compile + runtime)Dataclasses with from_dict parsersCodable enums/structs with CodingKeys
Event Delivery.on(type, handler) with unsubscribeChannel-based (events.recv().await).on(type, handler) with unsubscribeAsyncStream consumption in a for await loop
Wildcard Events.on("*", handler)ServerEvent::Unknown catch-all.on("*", handler).unknown catch-all case
Wire MappingcamelCase (native)#[serde(rename)] from snake_casefrom_dict with explicit key mappingCodingKeys with camelCase
Connection ManagementAuto-reconnect, ping timerManual (application controls lifecycle)Auto-reconnect, ping timerAuto-reconnect, ping timer
Primary Use CaseWeb apps, Node.js servicesTauri desktop apps, CLI toolsScripts, testing, notebooksmacOS/iOS apps, SwiftUI clients
Test Count137 tests178 tests173 tests193 tests

TypeScript SDK

The TypeScript SDK is a zero-dependency WebSocket client that works in any JavaScript runtime with a standard WebSocket global — browsers, Node.js 21+, Bun, and Deno. It provides a Promise-based API for request-response operations and typed event handlers for streaming events.

Installation

npm install @igentai/diminuendo-sdk

Quick Start

import { DiminuendoClient } from "@igentai/diminuendo-sdk"

const client = new DiminuendoClient({
  url: "ws://localhost:8080/ws",
  token: "eyJ...",
})

await client.connect()
const session = await client.createSession("coding-agent", "My Session")
const snapshot = await client.joinSession(session.id)

client.runTurn(session.id, "Explain the architecture")

client.on("text_delta", (event) => {
  process.stdout.write(event.text)
})

client.on("turn_complete", () => {
  console.log("\nDone.")
  client.disconnect()
})

Connection Lifecycle

const client = new DiminuendoClient({
  url: "ws://localhost:8080/ws",
  token: "your-jwt-token",        // Optional in dev mode
  autoReconnect: true,             // Default: true
  reconnectDelay: 1000,            // Default: 1000ms
  pingInterval: 30000,             // Default: 30000ms (0 to disable)
})

// connect() waits for the authentication handshake to complete
await client.connect()
console.log(client.authenticated)  // true
console.log(client.identity)       // { userId, email, tenantId }

// Disconnect and disable auto-reconnect
client.disconnect()
OptionTypeDefaultDescription
urlstring(required)Gateway WebSocket URL
tokenstring""JWT or API key for authentication
autoReconnectbooleantrueAutomatically reconnect on disconnect
reconnectDelaynumber1000Milliseconds between reconnection attempts
pingIntervalnumber30000Keepalive ping interval in ms. Set to 0 to disable
When autoReconnect is enabled, the client automatically re-establishes the connection and re-authenticates using the original token. Listen for lifecycle events to track reconnection:
client.on("connected", () => console.log("WebSocket opened"))
client.on("authenticated", () => console.log("Authenticated"))
client.on("disconnected", () => console.log("Disconnected -- will reconnect"))

Session Management

All session management methods return Promises and use request/response correlation — the SDK sends a typed message and waits for the corresponding server event.
// List all sessions
const sessions = await client.listSessions()
const allSessions = await client.listSessions(true)  // include archived

// Create a session
const session = await client.createSession("coding-agent", "My Session")

// Rename, archive, unarchive, delete
const renamed = await client.renameSession(session.id, "New Name")
const archived = await client.archiveSession(session.id)
const restored = await client.unarchiveSession(session.id)
await client.deleteSession(session.id)
Each method that modifies a session returns the updated SessionMeta:
interface SessionMeta {
  id: string
  tenantId: string
  name: string | null
  agentType: string
  status: SessionStatus
  archived: boolean
  createdAt: number
  updatedAt: number
  lastActivityAt: number | null
}

Session Interaction

// Join a session (subscribe to events, receive state snapshot)
const snapshot = await client.joinSession(sessionId)
console.log(snapshot.session.status)     // "inactive" | "ready" | "running" | ...
console.log(snapshot.recentHistory)      // Last N messages
console.log(snapshot.subscriberCount)    // Connected viewers

// Resume from a known sequence number (for reconnection)
const snapshot2 = await client.joinSession(sessionId, lastKnownSeq)

// Leave a session (fire-and-forget)
client.leaveSession(sessionId)

// Start an agent turn
client.runTurn(sessionId, "Fix the bug in auth.ts")
client.runTurn(sessionId, "Add tests", "client-turn-123")  // with client correlation ID

// Cancel an in-progress turn
client.stopTurn(sessionId)

// Mid-turn guidance
client.steer(sessionId, "Focus on the error handling, not the tests")

// Answer a question from the agent
client.answerQuestion(sessionId, requestId, {
  "preferred_language": "TypeScript",
  "framework": "React",
})

// Retrieve paginated history and events
const history = await client.getHistory(sessionId, 0, 50)
const events = await client.getEvents(sessionId, 0, 200)

// Measure round-trip latency
const { latencyMs } = await client.ping()
runTurn and stopTurn are fire-and-forget — they send the message immediately and return void. The response arrives as a stream of events (turn_started, text_delta, tool_call, turn_complete, etc.).

Event Handling

The SDK provides a type-safe event subscription system. The on() method accepts an event type string and a handler function, with TypeScript narrowing the event payload to the correct type.
// Type-safe event handler -- event is narrowed to TextDeltaEvent
const unsub = client.on("text_delta", (event) => {
  console.log(event.text)       // string
  console.log(event.turnId)     // string
  console.log(event.sessionId)  // string
})

// Wildcard handler -- receives all events
client.on("*", (event) => {
  console.log(event.type, event)
})

// One-shot handler
client.once("turn_complete", (event) => {
  console.log("Turn finished:", event.finalText)
})

// Remove all handlers for a type
client.off("text_delta")

// Unsubscribe a specific handler
unsub()

File Access

// List files in the agent's workspace
const files = await client.listFiles(sessionId)
const nested = await client.listFiles(sessionId, "src/", 2)

// Read a file
const content = await client.readFile(sessionId, "src/main.ts")

// Version history
const history = await client.fileHistory(sessionId, "src/main.ts")

// Read at a specific iteration
const oldVersion = await client.fileAtIteration(sessionId, "src/main.ts", 3)

Error Handling

Errors arise in two contexts:
  1. Connection errorsconnect() rejects if the WebSocket fails to open, authentication fails, or the 10-second timeout is reached.
  2. Request errors — methods like listSessions() or joinSession() reject if the gateway returns an error event, or if the 10-second request timeout is reached.
Sending a message on a closed WebSocket throws a synchronous Error("WebSocket not connected").
try {
  await client.connect()
} catch (err) {
  // "Connection timeout while waiting for authentication"
  // "Authentication required but no token was provided"
  // "AUTH_FAILED: Authentication failed"
}

try {
  await client.joinSession("nonexistent-id")
} catch (err) {
  // "SESSION_NOT_FOUND: Session does not exist"
  // "Request timeout for join_session"
}

Complete Example

import { DiminuendoClient } from "@igentai/diminuendo-sdk"

async function main() {
  const client = new DiminuendoClient({
    url: "ws://localhost:8080/ws",
    token: process.env.GATEWAY_TOKEN,
  })

  await client.connect()
  console.log(`Authenticated as ${client.identity?.email}`)

  const session = await client.createSession("coding-agent", "Bug Fix Session")
  const snapshot = await client.joinSession(session.id)
  console.log(`Session status: ${snapshot.session.status}`)

  client.on("turn_started", (e) => console.log(`Turn started: ${e.turnId}`))
  client.on("text_delta", (e) => process.stdout.write(e.text))
  client.on("tool_call", (e) => console.log(`\nTool: ${e.toolName}(${JSON.stringify(e.args)})`))
  client.on("tool_result", (e) => console.log(`Result [${e.status}]: ${e.output}`))
  client.on("turn_complete", () => {
    console.log("\nTurn complete.")
    client.disconnect()
  })
  client.on("turn_error", (e) => {
    console.error(`Turn error: ${e.message}`)
    client.disconnect()
  })

  client.runTurn(session.id, "Fix the authentication bug in src/auth.ts")
}

main().catch(console.error)

Protocol Coverage Matrix

Every client message and server event is implemented in all four SDKs. The following tables confirm full parity across the entire wire protocol surface.
Message TypeTypeScriptRustPythonSwift
authenticateYesYesYesYes
list_sessionsYesYesYesYes
create_sessionYesYesYesYes
rename_sessionYesYesYesYes
archive_sessionYesYesYesYes
unarchive_sessionYesYesYesYes
delete_sessionYesYesYesYes
join_sessionYesYesYesYes
leave_sessionYesYesYesYes
run_turnYesYesYesYes
stop_turnYesYesYesYes
steerYesYesYesYes
answer_questionYesYesYesYes
get_historyYesYesYesYes
get_eventsYesYesYesYes
pingYesYesYesYes
list_filesYesYesYesYes
read_fileYesYesYesYes
file_historyYesYesYesYes
file_at_iterationYesYesYesYes
manage_membersYesYesYesYes
Event TypeTypeScriptRustPythonSwift
welcomeYesYesYesYes
connectedYesYesYesYes
authenticatedYesYesYesYes
heartbeatYesYesYesYes
session_listYesYesYesYes
session_createdYesYesYesYes
session_updatedYesYesYesYes
session_archivedYesYesYesYes
session_unarchivedYesYesYesYes
session_deletedYesYesYesYes
session_stateYesYesYesYes
state_snapshotYesYesYesYes
stream_snapshotYesYesYesYes
turn_startedYesYesYesYes
text_deltaYesYesYesYes
turn_completeYesYesYesYes
turn_errorYesYesYesYes
message.deltaYesYesYesYes
message.completeYesYesYesYes
tool_callYesYesYesYes
tool_resultYesYesYesYes
tool_call_startYesYesYesYes
tool_call_deltaYesYesYesYes
tool_errorYesYesYesYes
question_requestedYesYesYesYes
permission_requestedYesYesYesYes
approval_resolvedYesYesYesYes
thinking_startYesYesYesYes
thinking_progressYesYesYesYes
thinking_completeYesYesYesYes
terminal_streamYesYesYesYes
terminal_completeYesYesYesYes
sandbox_initYesYesYesYes
sandbox_provisioningYesYesYesYes
sandbox_readyYesYesYesYes
sandbox_removedYesYesYesYes
usage_updateYesYesYesYes
usage_contextYesYesYesYes
gapYesYesYesYes
replay_completeYesYesYesYes
file_listYesYesYesYes
file_contentYesYesYesYes
file_history_resultYesYesYesYes
file_changedYesYesYesYes
steer_sentYesYesYesYes
stop_acknowledgedYesYesYesYes
server_shutdownYesYesYesYes
historyYesYesYesYes
eventsYesYesYesYes
member_listYesYesYesYes
member_updatedYesYesYesYes
member_removedYesYesYesYes
errorYesYesYesYes
pongYesYesYesYes

Testing Methodology

The SDK test suites do not exist to demonstrate that the code works. They exist to demonstrate that aggressive, systematic attempts to break the code have failed. This distinction — between confirmation and refutation — is the foundation of the testing methodology. The approach draws from Karl Popper’s philosophy of science: a claim is only as credible as the severity of the tests it has survived. A test that passes trivially provides no evidence of correctness.

Tests are Adversarial

Every test is written from the perspective of an attacker. What input would cause a crash? What timing would cause a race condition? What encoding would corrupt deserialization?

Claims are Falsifiable

“The client handles text_delta events” is not falsifiable. “A text_delta event with a 1MB text field is delivered to the handler without truncation” is falsifiable — and tested.

Mock Oracles, Not Mock Implementations

Each SDK test suite runs against a mock gateway that speaks the actual wire protocol. The mock is an oracle that validates conformance, not a stub returning canned responses.

No Confirmation Bias

Tests do not confirm expected behavior by feeding expected inputs. They probe boundaries: empty strings, null fields, maximum-size payloads, negative numbers, concurrent mutations.

Test Architecture per SDK

  • TypeScript: Runs a real Bun.serve WebSocket server implementing the full wire protocol. The mock gateway sends welcome, connected, and authenticated on connection, responds to all 21 message types, and simulates streaming event sequences.
  • Rust: Relies on serde’s compile-time guarantees plus runtime JSON round-trip verification. Every variant of ClientMessage (21) and ServerEvent (51) is tested for exact wire format conformance with explicit camelCase field assertions.
  • Python: Uses websockets.serve to run a mock gateway in the same asyncio event loop. Tests cover connection lifecycle, all session methods, dataclass parsing, ServerEvent properties, category predicates, and the event handler system.
  • Swift: Uses Swift Testing framework (@Test, #expect) with Codable round-trip verification. All 51 ServerEvent and 21 ClientMessage variants are tested for exact wire format conformance via CodingKeys.

Adversarial Patterns Tested

All four SDKs are tested against these adversarial scenarios:
  • Malformed server data: Invalid JSON, wrong field types, missing required fields, extra unknown fields
  • Request timeouts: Server never responds; pending promises/futures must reject, not hang
  • Large payloads: 1MB text fields, deeply nested JSON in tool args, thousands of sessions in a list
  • Unicode edge cases: Emoji, CJK, RTL, combining characters, zero-width characters, surrogate pairs
  • Numeric edge cases: seq: 0, seq: -1, seq: 9007199254740991 (JS MAX_SAFE_INTEGER), i64::MAX
  • Unknown event types: Forward-compatible handling via Unknown variant, wildcard handlers, or .unknown case
  • Concurrent operations: Multiple in-flight requests resolve to correct responses without cross-contamination

Coverage Statistics

SDKTestsAssertionsMessage TypesEvent TypesError Types
TypeScript137~60021/2151/51All
Rust178~80021/2151/51All
Python173~80021/2151/51All
Swift193~90021/2151/51All
Total (SDKs)681~3,10021/2151/51All
The gateway itself has an additional 9 integration tests that validate server-side protocol handling, session lifecycle, event mapping, and error paths against an in-process gateway instance with SQLite in-memory databases and mock Podium connections.

Running the Tests

bun test
Run the full test suite across all SDKs in a single command:
bun test && \
(cd sdk/typescript && bun test) && \
(cd sdk/rust && cargo test) && \
(cd sdk/python && python -m pytest tests/ -v) && \
(cd sdk/swift && swift test)
This is the complete validation that must pass before any code is merged.