Skip to main content
The @radaros/transport package provides createAgentRouter() to generate a fully-featured Express router with endpoints for all your agents, teams, and workflows.

Installation

npm install @radaros/transport express

Quick Start

Explicit wiring

Pass agents, teams, and workflows by name:
import express from "express";
import { Agent, openai } from "@radaros/core";
import { createAgentRouter } from "@radaros/transport";

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

const app = express();
app.use(express.json());

const router = createAgentRouter({
  agents: { assistant },
});

app.use("/api", router);
app.listen(3000);

Auto-discovery (zero-wiring)

Agents, teams, and workflows auto-register into a global registry when instantiated. The transport layer reads from this registry dynamically — entities created after the server starts are immediately available.
import express from "express";
import { Agent, openai } from "@radaros/core";
import { createAgentRouter } from "@radaros/transport";

const app = express();
app.use(express.json());
app.use("/api", createAgentRouter());  // no agents/teams/workflows needed

// Agents created anywhere auto-register and become available
new Agent({ name: "assistant", model: openai("gpt-4o") });
new Agent({ name: "analyst", model: openai("gpt-4o-mini") });

app.listen(3000);
// POST /api/agents/assistant/run  ✓
// POST /api/agents/analyst/run    ✓
You can also pass a mixed array via serve:
const router = createAgentRouter({
  serve: [assistant, analyst, researchTeam, pipeline],
});
This creates the following endpoints:
MethodPathDescription
POST/api/agents/:name/runRun agent, return JSON response
POST/api/agents/:name/streamStream response via Server-Sent Events
POST/api/teams/:name/runRun team
POST/api/teams/:name/streamStream team via SSE
POST/api/workflows/:name/runRun workflow
GET/api/agentsList registered agents with metadata
GET/api/teamsList registered teams
GET/api/workflowsList registered workflows
GET/api/registryList all registered names
GET/api/toolsList available tools (when toolkits/toolLibrary configured)
GET/api/tools/:nameGet single tool detail
GET/api/admin/mcpList MCP servers (when admin enabled)
POST/api/admin/mcpAdd + connect an MCP server
GET/api/admin/toolkitsList toolkit catalog

RouterOptions

agents
Record<string, Agent>
Map of named agents to expose. Each agent gets /agents/:name/run and /agents/:name/stream endpoints.
teams
Record<string, Team>
Map of named teams. Each gets /teams/:name/run and /teams/:name/stream endpoints.
workflows
Record<string, Workflow>
Map of named workflows. Each gets /workflows/:name/run endpoint.
serve
Servable[]
Mixed array of Agent, Team, and Workflow instances. Automatically classified and registered. An alternative to passing agents, teams, and workflows 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.
middleware
RequestHandler[]
Express middleware applied to all routes (e.g., auth, rate limiting).
swagger
SwaggerOptions
Enable Swagger UI. See Swagger docs.
fileUpload
boolean | FileUploadOptions
Enable multipart file upload for multi-modal input. See File Upload docs.
toolkits
Toolkit[]
Toolkit instances whose tools are exposed via GET /tools. Useful for UI tool discovery.
toolLibrary
Record<string, ToolDef>
Named tools exposed via GET /tools. Merged with toolkit tools (explicit entries take precedence).
admin
boolean | { mcpManager?: MCPManager }
Enable admin routes under /admin for managing MCP servers and the toolkit catalog at runtime. Pass true to use defaults, or provide a shared MCPManager instance. See MCP Admin.

Request Format

JSON Request

curl -X POST http://localhost:3000/api/agents/assistant/run \
  -H "Content-Type: application/json" \
  -d '{"input": "What is TypeScript?"}'

With Session

curl -X POST http://localhost:3000/api/agents/assistant/run \
  -H "Content-Type: application/json" \
  -d '{"input": "What was my last question?", "sessionId": "user-123"}'

With API Key

curl -X POST http://localhost:3000/api/agents/assistant/run \
  -H "Content-Type: application/json" \
  -H "x-openai-api-key: sk-your-key" \
  -d '{"input": "Hello"}'

Response Format

Run Response

{
  "text": "TypeScript is a typed superset of JavaScript...",
  "toolCalls": [],
  "usage": {
    "promptTokens": 25,
    "completionTokens": 150,
    "totalTokens": 175
  },
  "structured": null,
  "durationMs": 1234
}

Structured Output Response

When an agent has structuredOutput configured, the structured field contains the parsed and validated JSON object:
{
  "text": "{\"summary\":\"AI agents have advanced...\",\"keyPoints\":[...],\"confidence\":0.85}",
  "toolCalls": [],
  "usage": {
    "promptTokens": 82,
    "completionTokens": 129,
    "totalTokens": 211
  },
  "structured": {
    "summary": "AI agents have advanced significantly in 2026...",
    "keyPoints": [
      "Diagnostic accuracy improved by 20% with AI imaging tools.",
      "Customer service bots handle 70% of inquiries."
    ],
    "confidence": 0.85
  },
  "durationMs": 3026
}

Stream Response (SSE)

curl -X POST http://localhost:3000/api/agents/assistant/stream \
  -H "Content-Type: application/json" \
  -d '{"input": "Tell me a joke"}'
data: {"type":"text","text":"Why"}
data: {"type":"text","text":" did"}
data: {"type":"text","text":" the"}
...
data: {"type":"finish","finishReason":"stop"}

List Endpoints

When auto-discovery is enabled (default), the router exposes list endpoints with rich metadata:
# List all agents with model, provider, tools
curl http://localhost:3000/api/agents
[
  {
    "name": "assistant",
    "model": "gpt-4o",
    "provider": "openai",
    "tools": ["calculate"],
    "hasStructuredOutput": false
  }
]
# List all registered names
curl http://localhost:3000/api/registry
{
  "agents": ["assistant", "analyst"],
  "teams": ["research"],
  "workflows": ["pipeline"]
}

Full Example with Multiple Agents

import express from "express";
import { Agent, openai, google, defineTool } from "@radaros/core";
import { createAgentRouter } from "@radaros/transport";
import { z } from "zod";

const calculator = defineTool({
  name: "calculate",
  description: "Evaluate a math expression",
  parameters: z.object({ expression: z.string() }),
  execute: async ({ expression }) => String(eval(expression)),
});

// Agents auto-register into the global registry
const assistant = new Agent({
  name: "assistant",
  model: openai("gpt-4o"),
  instructions: "You are a helpful assistant.",
  tools: [calculator],
});

const analyst = new Agent({
  name: "analyst",
  model: openai("gpt-4o-mini"),
  instructions: "You analyze data and provide insights.",
  structuredOutput: z.object({
    summary: z.string(),
    sentiment: z.enum(["positive", "negative", "neutral"]),
    confidence: z.number(),
  }),
});

const vision = new Agent({
  name: "vision",
  model: google("gemini-2.5-flash"),
  instructions: "You analyze images and describe what you see.",
});

const app = express();
app.use(express.json());

// Auto-discovery: no need to pass agents explicitly
const router = createAgentRouter({
  swagger: {
    enabled: true,
    title: "My AI API",
    description: "Multi-agent API powered by RadarOS",
  },
  fileUpload: true,
});

app.use("/api", router);

app.listen(3000, () => {
  console.log("API running on http://localhost:3000");
  console.log("Swagger UI at http://localhost:3000/api/docs");
});

Adding Custom Middleware

const authMiddleware = (req, res, next) => {
  const token = req.headers.authorization;
  if (!token) return res.status(401).json({ error: "Unauthorized" });
  next();
};

const router = createAgentRouter({
  agents: { assistant },
  middleware: [authMiddleware],
});

Error Handling

import { errorHandler } from "@radaros/transport";

app.use("/api", router);
app.use(errorHandler);
The errorHandler middleware catches errors and returns structured JSON responses:
{
  "error": "Agent \"unknown\" not found"
}