Skip to main content

Socket.IO Gateway

createAgentGateway() attaches real-time handlers to a Socket.IO server. Clients emit agent.run or team.run and receive streaming chunks, tool events, and final output over WebSockets.

Installation

npm install @radaros/transport socket.io

Basic Setup

Explicit wiring

import { createServer } from "http";
import { Server } from "socket.io";
import { createAgentGateway } from "@radaros/transport";
import { Agent, openai } from "@radaros/core";

const agent = new Agent({
  name: "Assistant",
  model: openai("gpt-4o"),
  instructions: "You are helpful.",
});

const httpServer = createServer();
const io = new Server(httpServer);

createAgentGateway({
  io,
  agents: { assistant: agent },
  namespace: "/radaros", // default
});

httpServer.listen(3000);

Auto-discovery (zero-wiring)

Agents and teams auto-register into a global registry. The gateway reads from it dynamically — entities created after the gateway starts are immediately reachable.
import { createServer } from "http";
import { Server } from "socket.io";
import { createAgentGateway } from "@radaros/transport";
import { Agent, openai } from "@radaros/core";

const httpServer = createServer();
const io = new Server(httpServer);

createAgentGateway({ io }); // no agents/teams needed

// Created later — immediately available via Socket.IO
new Agent({ name: "assistant", model: openai("gpt-4o") });

httpServer.listen(3000);
You can also pass a mixed array via serve:
createAgentGateway({
  io,
  serve: [assistant, analyst, researchTeam],
});

GatewayOptions

io
Server
required
Socket.IO server instance.
agents
Record<string, Agent>
Map of agent names to Agent instances.
teams
Record<string, Team>
Map of team names to Team instances.
serve
Servable[]
Mixed array of Agent and Team instances. Automatically classified. An alternative to passing agents and teams separately.
registry
Registry | false
Controls live auto-discovery. Defaults to the global registry — all auto-registered entities are available. Pass a custom Registry instance, or false to disable auto-discovery and only serve explicitly passed entities.
namespace
string
default:"/radaros"
Socket.IO namespace. Clients connect to http://host/radaros (or your namespace).
authMiddleware
(socket, next) => void
Socket.IO middleware for authentication. Call next() to allow, or next(new Error("Unauthorized")) to reject.
toolkits
Toolkit[]
Toolkit instances whose tools are exposed via tools.list event. Useful for UI tool discovery.
toolLibrary
Record<string, ToolDef>
Named tools exposed via tools.list. Merged with toolkit tools (explicit entries take precedence).

Events

Client → Server

EventPayloadDescription
agent.run{ name, input, sessionId?, apiKey? }Run agent and stream response
team.run{ name, input, sessionId?, apiKey? }Run team (non-streaming)
agents.list{}List registered agents with metadata (callback)
teams.list{}List registered teams (callback)
workflows.list{}List registered workflows (callback)
registry.list{}List all registered entity names (callback)
tools.list{}List available tools (when toolkits configured)
tools.get{ name }Get single tool detail

Server → Client

EventPayloadDescription
agent.chunk{ chunk: string }Streamed text chunk
agent.tool.call{ toolName, args }Tool call started
agent.tool.done{ toolCallId }Tool call finished
agent.done{ output: { text, usage? } }Run complete — usage includes providerMetrics with raw API data
agent.error{ error: string }Error occurred

Listing Agents & Teams

Use acknowledgement callbacks to query registered entities:
socket.emit("agents.list", {}, (agents) => {
  console.log(agents);
  // [{ name: "assistant", model: "gpt-4o", provider: "openai", tools: ["search"], hasStructuredOutput: false }]
});

socket.emit("teams.list", {}, (teams) => {
  console.log(teams);
  // [{ name: "research" }]
});

socket.emit("registry.list", {}, (data) => {
  console.log(data);
  // { agents: ["assistant"], teams: ["research"], workflows: [] }
});

Real-Time Chat Example

Server:
import { createServer } from "http";
import { Server } from "socket.io";
import { createAgentGateway } from "@radaros/transport";
import { Agent, openai } from "@radaros/core";

const agent = new Agent({
  name: "ChatBot",
  model: openai("gpt-4o"),
  instructions: "You are a friendly chat assistant.",
});

const httpServer = createServer();
const io = new Server(httpServer, {
  cors: { origin: "*" },
});

createAgentGateway({
  io,
  agents: { chatbot: agent },
  namespace: "/chat",
});

httpServer.listen(3000);
Client (browser):
<script src="https://cdn.socket.io/4.8.3/socket.io.min.js"></script>
<script>
  const socket = io("http://localhost:3000/chat");

  socket.on("agent.chunk", ({ chunk }) => {
    document.getElementById("output").textContent += chunk;
  });

  socket.on("agent.done", ({ output }) => {
    console.log("Done:", output.text);
  });

  socket.on("agent.error", ({ error }) => {
    console.error("Error:", error);
  });

  socket.emit("agent.run", {
    name: "chatbot",
    input: "Tell me a joke.",
    sessionId: "user-123",
  });
</script>

With Authentication

createAgentGateway({
  io,
  agents: { assistant: agent },
  authMiddleware: (socket, next) => {
    const token = socket.handshake.auth?.token;
    if (!token || !validateToken(token)) {
      return next(new Error("Unauthorized"));
    }
    socket.userId = decodeToken(token).userId;
    next();
  },
});
Clients can pass apiKey in the event payload or in handshake.auth:
const socket = io("http://localhost:3000/radaros", {
  auth: { apiKey: "sk-..." },
});

socket.emit("agent.run", {
  name: "assistant",
  input: "Hello",
  apiKey: "sk-...", // or use handshake.auth.apiKey
});

Session Continuity

Use sessionId to maintain conversation context across multiple agent.run calls:
const sessionId = `user-${userId}-${Date.now()}`;

socket.emit("agent.run", {
  name: "assistant",
  input: "My name is Alice.",
  sessionId,
});

// Later, same session
socket.emit("agent.run", {
  name: "assistant",
  input: "What's my name?",
  sessionId,
});

Voice Gateway

For real-time voice over Socket.IO, see the dedicated Voice Agents docs. The createVoiceGateway() function streams audio between browser clients and VoiceAgent instances using the same Socket.IO server.
import { VoiceAgent, openaiRealtime } from "@radaros/core";
import { createVoiceGateway } from "@radaros/transport";

const agent = new VoiceAgent({
  name: "assistant",
  provider: openaiRealtime("gpt-4o-realtime-preview"),
  instructions: "You are a voice assistant.",
  voice: "alloy",
});

createVoiceGateway({
  agents: { assistant: agent },
  io,
  namespace: "/voice",
});

Voice Gateway Security

The voice gateway validates all incoming data to prevent abuse:
  • Audio data (voice.audio): Must be a string with a maximum size limit. Buffer.from() decoding is wrapped in try/catch to handle malformed base64 data.
  • Text data (voice.text): Must be a string with a maximum length limit.
  • Session cleanup: session.close() errors in voice.stop are caught and logged instead of crashing the server.

Browser Gateway

For live browser agent observation over Socket.IO, see the Browser Agents docs. The createBrowserGateway() function streams screenshots, actions, and step events from BrowserAgent runs to connected clients.
import { BrowserAgent } from "@radaros/browser";
import { createBrowserGateway } from "@radaros/transport";

createBrowserGateway({
  agents: { browser: agent },
  io,
  namespace: "/browser",
  streamScreenshots: true,
});