TypeScript SDK

The Diminuendo TypeScript SDK provides a fully typed WebSocket client for connecting to the gateway from any JavaScript runtime. It has zero runtime dependencies beyond the platform’s native WebSocket implementation, and works in Node.js, Bun, Deno, and browsers.

Installation

npm install @igentai/diminuendo-sdk
Or, if referencing from the monorepo:
import { DiminuendoClient } from "@igentai/diminuendo-sdk"

DiminuendoClient

The DiminuendoClient class is the primary entry point. It manages the WebSocket connection, authentication handshake, event dispatching, ping keepalive, and request/response correlation.

Constructor

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

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)
})
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

Connection Lifecycle

connect()

Establishes the WebSocket connection and waits for the authentication handshake to complete. The returned Promise resolves once the gateway sends an authenticated event (or, in dev mode, once the welcome event indicates no authentication is required).
await client.connect()
console.log(client.authenticated) // true
console.log(client.identity)      // { userId, email, tenantId }
If authentication fails or the connection times out (10 seconds), the Promise rejects with an Error.

disconnect()

Closes the WebSocket connection and disables auto-reconnect. Clears the ping timer and all pending request chains.
client.disconnect()

Properties

PropertyTypeDescription
connectedbooleanWhether the WebSocket is currently open
authenticatedbooleanWhether authentication has completed
identity{ userId, email, tenantId } | nullThe authenticated identity

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("echo", "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

joinSession / leaveSession

Joining a session subscribes the client to that session’s event stream and returns a StateSnapshotEvent containing the current session state, recent history, and subscriber count.
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)    // Number of connected viewers

// Resume from a known sequence number (for reconnection)
const snapshot2 = await client.joinSession(sessionId, lastKnownSeq)
Leaving a session unsubscribes from events. This is a fire-and-forget operation:
client.leaveSession(sessionId)

runTurn / stopTurn

Start an agent turn with a user message. Events will arrive via the event handlers (see below). An optional clientTurnId can be provided for client-side correlation.
client.runTurn(sessionId, "Fix the bug in auth.ts")
client.runTurn(sessionId, "Add tests", "client-turn-123")

// Cancel an in-progress turn
client.stopTurn(sessionId)
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.).

steer

Send mid-turn guidance to the agent while it is processing:
client.steer(sessionId, "Focus on the error handling, not the tests")

answerQuestion

Respond to a question_requested event from the agent:
client.answerQuestion(sessionId, requestId, {
  "preferred_language": "TypeScript",
  "framework": "React",
})

// Dismiss the question without answering
client.answerQuestion(sessionId, requestId, {}, true)

getHistory / getEvents

Retrieve paginated conversation history or raw events:
const history = await client.getHistory(sessionId, 0, 50)
// history = [{ id, role, content, createdAt, metadata }, ...]

const events = await client.getEvents(sessionId, 0, 200)
// events = [{ type, sessionId, turnId, seq, ts, ... }, ...]

ping

Measure round-trip latency to the gateway:
const { latencyMs } = await client.ping()
console.log(`Latency: ${latencyMs}ms`)

File Access

Access files in the agent’s workspace:
// List files
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")
console.log(content.content)    // File text
console.log(content.encoding)   // "utf-8"

// Version history
const history = await client.fileHistory(sessionId, "src/main.ts")
// history = [{ iteration, timestamp, size, hash }, ...]

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

Member Management

Manage tenant members (requires appropriate role):
const result = await client.manageMembers("list")
const updated = await client.manageMembers("set_role", userId, "admin")
const removed = await client.manageMembers("remove", userId)

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 via Extract<ServerEvent, { type: T }>.
// 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()
Every on() call returns an unsubscribe function. Calling it removes that specific handler without affecting other handlers for the same event type.

Auto-Reconnect

When autoReconnect is enabled (the default), the client will automatically attempt to reconnect after the WebSocket closes. The reconnection uses the configured reconnectDelay and will re-authenticate using the original token. Listen for connection 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"))

Error Handling

The SDK raises errors 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,
  })

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

  // Create a session
  const session = await client.createSession("coding-agent", "Bug Fix Session")
  console.log(`Created session: ${session.id}`)

  // Join and get current state
  const snapshot = await client.joinSession(session.id)
  console.log(`Session status: ${snapshot.session.status}`)

  // Listen for events
  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 call: ${e.toolName}(${JSON.stringify(e.args)})`)
  })

  client.on("tool_result", (e) => {
    console.log(`Tool result [${e.status}]: ${e.output}`)
  })

  client.on("turn_complete", (e) => {
    console.log(`\nTurn complete.`)
    client.disconnect()
  })

  client.on("turn_error", (e) => {
    console.error(`Turn error: ${e.message}`)
    client.disconnect()
  })

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

main().catch(console.error)