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)
})
| Option | Type | Default | Description |
|---|
url | string | (required) | Gateway WebSocket URL |
token | string | "" | JWT or API key for authentication |
autoReconnect | boolean | true | Automatically reconnect on disconnect |
reconnectDelay | number | 1000 | Milliseconds between reconnection attempts |
pingInterval | number | 30000 | Keepalive 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.
Properties
| Property | Type | Description |
|---|
connected | boolean | Whether the WebSocket is currently open |
authenticated | boolean | Whether authentication has completed |
identity | { userId, email, tenantId } | null | The 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:
- Connection errors —
connect() rejects if the WebSocket fails to open, authentication fails, or the 10-second timeout is reached.
- 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)