@radaros/transport 0.3.13 → 0.3.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/dist/index.d.ts +45 -1
- package/dist/index.js +231 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -29,8 +29,9 @@ app.listen(3000);
|
|
|
29
29
|
|
|
30
30
|
## Features
|
|
31
31
|
|
|
32
|
-
- **
|
|
33
|
-
- **
|
|
32
|
+
- **Auto-Discovery** — Reads from the global `Registry` at request time; agents created after server start are immediately available
|
|
33
|
+
- **Express Router** — REST API with streaming support and list endpoints (`GET /agents`, `/teams`, `/workflows`, `/tools`)
|
|
34
|
+
- **Socket.IO Gateway** — Real-time WebSocket communication with dynamic agent/team lookup and tool discovery
|
|
34
35
|
- **A2A Server** — Agent-to-Agent protocol support
|
|
35
36
|
- **CORS & Rate Limiting** — Built-in security middleware
|
|
36
37
|
- **Swagger** — Auto-generated API documentation
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Agent, A2AAgentCard, Team, Workflow, EventBus, VoiceAgent } from '@radaros/core';
|
|
1
|
+
import { Agent, A2AAgentCard, Registry, Servable, Team, Workflow, ToolDef, Toolkit, EventBus, VoiceAgent } from '@radaros/core';
|
|
2
2
|
|
|
3
3
|
interface A2AServerOptions {
|
|
4
4
|
agents: Record<string, Agent>;
|
|
@@ -71,6 +71,24 @@ interface SwaggerOptions {
|
|
|
71
71
|
specPath?: string;
|
|
72
72
|
}
|
|
73
73
|
interface RouterOptions {
|
|
74
|
+
/**
|
|
75
|
+
* Use a Registry for live auto-discovery. The router creates dynamic routes
|
|
76
|
+
* that resolve agents/teams/workflows at request time — any instance created
|
|
77
|
+
* after the router is mounted is automatically available.
|
|
78
|
+
*
|
|
79
|
+
* When omitted, falls back to the global registry from `@radaros/core`.
|
|
80
|
+
* Pass `false` to disable registry-based routing entirely (use explicit maps only).
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* createAgentRouter({ cors: true });
|
|
84
|
+
* new Agent({ name: "bot", model: openai("gpt-4o") }); // immediately routable
|
|
85
|
+
*/
|
|
86
|
+
registry?: Registry | false;
|
|
87
|
+
/**
|
|
88
|
+
* Auto-discover agents, teams, and workflows from a mixed array.
|
|
89
|
+
* Each item is classified by its `.kind` and keyed by `.name`.
|
|
90
|
+
*/
|
|
91
|
+
serve?: Servable[];
|
|
74
92
|
agents?: Record<string, Agent>;
|
|
75
93
|
teams?: Record<string, Team>;
|
|
76
94
|
workflows?: Record<string, Workflow<any>>;
|
|
@@ -86,6 +104,10 @@ interface RouterOptions {
|
|
|
86
104
|
windowMs?: number;
|
|
87
105
|
max?: number;
|
|
88
106
|
} | boolean;
|
|
107
|
+
/** Named tool library exposed via GET /tools. Tools from toolkits are auto-collected. */
|
|
108
|
+
toolLibrary?: Record<string, ToolDef>;
|
|
109
|
+
/** Toolkit instances whose tools are exposed via GET /tools. Merged with toolLibrary. */
|
|
110
|
+
toolkits?: Toolkit[];
|
|
89
111
|
}
|
|
90
112
|
|
|
91
113
|
declare function createAgentRouter(opts: RouterOptions): any;
|
|
@@ -175,6 +197,24 @@ interface BrowserGatewayOptions {
|
|
|
175
197
|
declare function createBrowserGateway(opts: BrowserGatewayOptions): void;
|
|
176
198
|
|
|
177
199
|
interface GatewayOptions {
|
|
200
|
+
/**
|
|
201
|
+
* Use a Registry for live auto-discovery. The gateway resolves agents/teams
|
|
202
|
+
* at event time — any instance created after the gateway starts is automatically
|
|
203
|
+
* reachable.
|
|
204
|
+
*
|
|
205
|
+
* When omitted, falls back to the global registry from `@radaros/core`.
|
|
206
|
+
* Pass `false` to disable registry-based lookup (use explicit maps only).
|
|
207
|
+
*
|
|
208
|
+
* @example
|
|
209
|
+
* createAgentGateway({ io });
|
|
210
|
+
* new Agent({ name: "bot", model: openai("gpt-4o") }); // immediately reachable
|
|
211
|
+
*/
|
|
212
|
+
registry?: Registry | false;
|
|
213
|
+
/**
|
|
214
|
+
* Auto-discover agents and teams from a mixed array.
|
|
215
|
+
* Each item is classified by its `.kind` and keyed by `.name`.
|
|
216
|
+
*/
|
|
217
|
+
serve?: Servable[];
|
|
178
218
|
agents?: Record<string, Agent>;
|
|
179
219
|
teams?: Record<string, Team>;
|
|
180
220
|
io: any;
|
|
@@ -182,6 +222,10 @@ interface GatewayOptions {
|
|
|
182
222
|
authMiddleware?: (socket: any, next: (err?: Error) => void) => void;
|
|
183
223
|
/** Max requests per minute per socket. Default: 60 */
|
|
184
224
|
maxRequestsPerMinute?: number;
|
|
225
|
+
/** Named tool library exposed via tools.list event. */
|
|
226
|
+
toolLibrary?: Record<string, ToolDef>;
|
|
227
|
+
/** Toolkit instances whose tools are exposed via tools.list. Merged with toolLibrary. */
|
|
228
|
+
toolkits?: Toolkit[];
|
|
185
229
|
}
|
|
186
230
|
|
|
187
231
|
declare function createAgentGateway(opts: GatewayOptions): void;
|
package/dist/index.js
CHANGED
|
@@ -431,6 +431,7 @@ function requestLogger(options) {
|
|
|
431
431
|
|
|
432
432
|
// src/express/router-factory.ts
|
|
433
433
|
import { createRequire as createRequire4 } from "module";
|
|
434
|
+
import { classifyServables, collectToolkitTools, describeToolLibrary, registry as globalRegistry } from "@radaros/core";
|
|
434
435
|
|
|
435
436
|
// src/express/swagger.ts
|
|
436
437
|
import { createRequire as createRequire3 } from "module";
|
|
@@ -955,6 +956,16 @@ function extractApiKey(req, agent) {
|
|
|
955
956
|
return req.body?.apiKey ?? void 0;
|
|
956
957
|
}
|
|
957
958
|
function createAgentRouter(opts) {
|
|
959
|
+
if (opts.serve?.length) {
|
|
960
|
+
const discovered = classifyServables(opts.serve);
|
|
961
|
+
opts = {
|
|
962
|
+
...opts,
|
|
963
|
+
agents: { ...discovered.agents, ...opts.agents },
|
|
964
|
+
teams: { ...discovered.teams, ...opts.teams },
|
|
965
|
+
workflows: { ...discovered.workflows, ...opts.workflows }
|
|
966
|
+
};
|
|
967
|
+
}
|
|
968
|
+
const reg = opts.registry === false ? null : opts.registry ?? globalRegistry;
|
|
958
969
|
let express;
|
|
959
970
|
try {
|
|
960
971
|
express = _require4("express");
|
|
@@ -1144,6 +1155,154 @@ function createAgentRouter(opts) {
|
|
|
1144
1155
|
});
|
|
1145
1156
|
}
|
|
1146
1157
|
}
|
|
1158
|
+
if (reg) {
|
|
1159
|
+
router.post(
|
|
1160
|
+
"/agents/:name/run",
|
|
1161
|
+
withUpload(async (req, res) => {
|
|
1162
|
+
const agent = reg.getAgent(req.params.name);
|
|
1163
|
+
if (!agent) return res.status(404).json({ error: `Agent "${req.params.name}" not found` });
|
|
1164
|
+
try {
|
|
1165
|
+
const validated = validateBody(req.body, { input: "string", sessionId: "string?", userId: "string?" });
|
|
1166
|
+
const input = buildMultiModalInput(req.body, req.files) ?? validated.input;
|
|
1167
|
+
if (!input) return res.status(400).json({ error: "input is required" });
|
|
1168
|
+
const apiKey = extractApiKey(req, agent);
|
|
1169
|
+
const result = await agent.run(input, {
|
|
1170
|
+
sessionId: validated.sessionId,
|
|
1171
|
+
userId: validated.userId,
|
|
1172
|
+
apiKey
|
|
1173
|
+
});
|
|
1174
|
+
res.json(result);
|
|
1175
|
+
} catch (error) {
|
|
1176
|
+
res.status(400).json({ error: error.message });
|
|
1177
|
+
}
|
|
1178
|
+
})
|
|
1179
|
+
);
|
|
1180
|
+
router.post("/agents/:name/stream", async (req, res) => {
|
|
1181
|
+
const agent = reg.getAgent(req.params.name);
|
|
1182
|
+
if (!agent) return res.status(404).json({ error: `Agent "${req.params.name}" not found` });
|
|
1183
|
+
try {
|
|
1184
|
+
const validated = validateBody(req.body, { input: "string", sessionId: "string?", userId: "string?" });
|
|
1185
|
+
const input = validated.input;
|
|
1186
|
+
if (!input) return res.status(400).json({ error: "input is required" });
|
|
1187
|
+
const apiKey = extractApiKey(req, agent);
|
|
1188
|
+
res.writeHead(200, {
|
|
1189
|
+
"Content-Type": "text/event-stream",
|
|
1190
|
+
"Cache-Control": "no-cache",
|
|
1191
|
+
Connection: "keep-alive"
|
|
1192
|
+
});
|
|
1193
|
+
for await (const chunk of agent.stream(input, {
|
|
1194
|
+
sessionId: validated.sessionId,
|
|
1195
|
+
userId: validated.userId,
|
|
1196
|
+
apiKey
|
|
1197
|
+
})) {
|
|
1198
|
+
res.write(`data: ${JSON.stringify(chunk)}
|
|
1199
|
+
|
|
1200
|
+
`);
|
|
1201
|
+
}
|
|
1202
|
+
res.write("data: [DONE]\n\n");
|
|
1203
|
+
res.end();
|
|
1204
|
+
} catch (error) {
|
|
1205
|
+
if (!res.headersSent) res.status(500).json({ error: error.message });
|
|
1206
|
+
else {
|
|
1207
|
+
res.write(`data: ${JSON.stringify({ type: "error", error: error.message })}
|
|
1208
|
+
|
|
1209
|
+
`);
|
|
1210
|
+
res.end();
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
});
|
|
1214
|
+
router.post("/teams/:name/run", async (req, res) => {
|
|
1215
|
+
const team = reg.getTeam(req.params.name);
|
|
1216
|
+
if (!team) return res.status(404).json({ error: `Team "${req.params.name}" not found` });
|
|
1217
|
+
try {
|
|
1218
|
+
const validated = validateBody(req.body, { input: "string", sessionId: "string?", userId: "string?" });
|
|
1219
|
+
const input = validated.input;
|
|
1220
|
+
if (!input) return res.status(400).json({ error: "input is required" });
|
|
1221
|
+
const apiKey = req.headers["x-api-key"] ?? req.body?.apiKey;
|
|
1222
|
+
const result = await team.run(input, {
|
|
1223
|
+
sessionId: validated.sessionId,
|
|
1224
|
+
userId: validated.userId,
|
|
1225
|
+
apiKey
|
|
1226
|
+
});
|
|
1227
|
+
res.json(result);
|
|
1228
|
+
} catch (error) {
|
|
1229
|
+
res.status(500).json({ error: error.message });
|
|
1230
|
+
}
|
|
1231
|
+
});
|
|
1232
|
+
router.post("/teams/:name/stream", async (req, res) => {
|
|
1233
|
+
const team = reg.getTeam(req.params.name);
|
|
1234
|
+
if (!team) return res.status(404).json({ error: `Team "${req.params.name}" not found` });
|
|
1235
|
+
try {
|
|
1236
|
+
const validated = validateBody(req.body, { input: "string", sessionId: "string?", userId: "string?" });
|
|
1237
|
+
const input = validated.input;
|
|
1238
|
+
if (!input) return res.status(400).json({ error: "input is required" });
|
|
1239
|
+
const apiKey = req.headers["x-api-key"] ?? req.body?.apiKey;
|
|
1240
|
+
res.writeHead(200, {
|
|
1241
|
+
"Content-Type": "text/event-stream",
|
|
1242
|
+
"Cache-Control": "no-cache",
|
|
1243
|
+
Connection: "keep-alive"
|
|
1244
|
+
});
|
|
1245
|
+
for await (const chunk of team.stream(input, {
|
|
1246
|
+
sessionId: validated.sessionId,
|
|
1247
|
+
userId: validated.userId,
|
|
1248
|
+
apiKey
|
|
1249
|
+
})) {
|
|
1250
|
+
res.write(`data: ${JSON.stringify(chunk)}
|
|
1251
|
+
|
|
1252
|
+
`);
|
|
1253
|
+
}
|
|
1254
|
+
res.write("data: [DONE]\n\n");
|
|
1255
|
+
res.end();
|
|
1256
|
+
} catch (error) {
|
|
1257
|
+
if (!res.headersSent) res.status(500).json({ error: error.message });
|
|
1258
|
+
else {
|
|
1259
|
+
res.write(`data: ${JSON.stringify({ type: "error", error: error.message })}
|
|
1260
|
+
|
|
1261
|
+
`);
|
|
1262
|
+
res.end();
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
});
|
|
1266
|
+
router.post("/workflows/:name/run", async (req, res) => {
|
|
1267
|
+
const workflow = reg.getWorkflow(req.params.name);
|
|
1268
|
+
if (!workflow) return res.status(404).json({ error: `Workflow "${req.params.name}" not found` });
|
|
1269
|
+
try {
|
|
1270
|
+
const { sessionId, userId } = req.body ?? {};
|
|
1271
|
+
const result = await workflow.run({ sessionId, userId });
|
|
1272
|
+
res.json(result);
|
|
1273
|
+
} catch (error) {
|
|
1274
|
+
res.status(500).json({ error: error.message });
|
|
1275
|
+
}
|
|
1276
|
+
});
|
|
1277
|
+
router.get("/agents", (_req, res) => {
|
|
1278
|
+
res.json(reg.describeAgents());
|
|
1279
|
+
});
|
|
1280
|
+
router.get("/teams", (_req, res) => {
|
|
1281
|
+
res.json(reg.describeTeams());
|
|
1282
|
+
});
|
|
1283
|
+
router.get("/workflows", (_req, res) => {
|
|
1284
|
+
res.json(reg.describeWorkflows());
|
|
1285
|
+
});
|
|
1286
|
+
router.get("/registry", (_req, res) => {
|
|
1287
|
+
res.json(reg.list());
|
|
1288
|
+
});
|
|
1289
|
+
}
|
|
1290
|
+
const fromToolkits = opts.toolkits ? collectToolkitTools(opts.toolkits) : {};
|
|
1291
|
+
const mergedTools = { ...fromToolkits, ...opts.toolLibrary ?? {} };
|
|
1292
|
+
if (Object.keys(mergedTools).length > 0) {
|
|
1293
|
+
router.get("/tools", (_req, res) => {
|
|
1294
|
+
res.json(describeToolLibrary(mergedTools));
|
|
1295
|
+
});
|
|
1296
|
+
router.get("/tools/:name", (req, res) => {
|
|
1297
|
+
const tool = mergedTools[req.params.name];
|
|
1298
|
+
if (!tool) return res.status(404).json({ error: `Tool "${req.params.name}" not found` });
|
|
1299
|
+
res.json({
|
|
1300
|
+
name: tool.name,
|
|
1301
|
+
description: tool.description,
|
|
1302
|
+
parameters: Object.keys(tool.parameters.shape ?? {})
|
|
1303
|
+
});
|
|
1304
|
+
});
|
|
1305
|
+
}
|
|
1147
1306
|
return router;
|
|
1148
1307
|
}
|
|
1149
1308
|
|
|
@@ -1261,6 +1420,7 @@ function createBrowserGateway(opts) {
|
|
|
1261
1420
|
}
|
|
1262
1421
|
|
|
1263
1422
|
// src/socketio/gateway.ts
|
|
1423
|
+
import { classifyServables as classifyServables2, collectToolkitTools as collectToolkitTools2, describeToolLibrary as describeToolLibrary2, registry as globalRegistry2 } from "@radaros/core";
|
|
1264
1424
|
function createSocketRateLimiter(maxPerMinute = 60) {
|
|
1265
1425
|
return () => {
|
|
1266
1426
|
let count = 0;
|
|
@@ -1277,6 +1437,15 @@ function createSocketRateLimiter(maxPerMinute = 60) {
|
|
|
1277
1437
|
};
|
|
1278
1438
|
}
|
|
1279
1439
|
function createAgentGateway(opts) {
|
|
1440
|
+
if (opts.serve?.length) {
|
|
1441
|
+
const discovered = classifyServables2(opts.serve);
|
|
1442
|
+
opts = {
|
|
1443
|
+
...opts,
|
|
1444
|
+
agents: { ...discovered.agents, ...opts.agents },
|
|
1445
|
+
teams: { ...discovered.teams, ...opts.teams }
|
|
1446
|
+
};
|
|
1447
|
+
}
|
|
1448
|
+
const reg = opts.registry === false ? null : opts.registry ?? globalRegistry2;
|
|
1280
1449
|
const ns = opts.io.of(opts.namespace ?? "/radaros");
|
|
1281
1450
|
if (opts.authMiddleware) {
|
|
1282
1451
|
ns.use(opts.authMiddleware);
|
|
@@ -1297,7 +1466,7 @@ function createAgentGateway(opts) {
|
|
|
1297
1466
|
socket.emit("agent.error", { error: "Invalid sessionId: must be a string" });
|
|
1298
1467
|
return;
|
|
1299
1468
|
}
|
|
1300
|
-
const agent = opts.agents?.[data.name];
|
|
1469
|
+
const agent = opts.agents?.[data.name] ?? reg?.getAgent(data.name);
|
|
1301
1470
|
if (!agent) {
|
|
1302
1471
|
socket.emit("agent.error", {
|
|
1303
1472
|
error: `Agent "${data.name}" not found`
|
|
@@ -1329,10 +1498,70 @@ function createAgentGateway(opts) {
|
|
|
1329
1498
|
socket.emit("agent.error", { error: error.message });
|
|
1330
1499
|
}
|
|
1331
1500
|
});
|
|
1501
|
+
socket.on("agents.list", (_data, ack) => {
|
|
1502
|
+
if (reg) {
|
|
1503
|
+
ack?.(reg.describeAgents());
|
|
1504
|
+
} else {
|
|
1505
|
+
const names = Object.keys(opts.agents ?? {});
|
|
1506
|
+
ack?.(names.map((n) => ({ name: n })));
|
|
1507
|
+
}
|
|
1508
|
+
});
|
|
1509
|
+
socket.on("teams.list", (_data, ack) => {
|
|
1510
|
+
if (reg) {
|
|
1511
|
+
ack?.(reg.describeTeams());
|
|
1512
|
+
} else {
|
|
1513
|
+
const names = Object.keys(opts.teams ?? {});
|
|
1514
|
+
ack?.(names.map((n) => ({ name: n })));
|
|
1515
|
+
}
|
|
1516
|
+
});
|
|
1517
|
+
socket.on("workflows.list", (_data, ack) => {
|
|
1518
|
+
if (reg) {
|
|
1519
|
+
ack?.(reg.describeWorkflows());
|
|
1520
|
+
} else {
|
|
1521
|
+
ack?.([]);
|
|
1522
|
+
}
|
|
1523
|
+
});
|
|
1524
|
+
socket.on("registry.list", (_data, ack) => {
|
|
1525
|
+
if (reg) {
|
|
1526
|
+
ack?.(reg.list());
|
|
1527
|
+
} else {
|
|
1528
|
+
ack?.({
|
|
1529
|
+
agents: Object.keys(opts.agents ?? {}),
|
|
1530
|
+
teams: Object.keys(opts.teams ?? {}),
|
|
1531
|
+
workflows: []
|
|
1532
|
+
});
|
|
1533
|
+
}
|
|
1534
|
+
});
|
|
1535
|
+
const fromToolkits = opts.toolkits ? collectToolkitTools2(opts.toolkits) : {};
|
|
1536
|
+
const mergedTools = { ...fromToolkits, ...opts.toolLibrary ?? {} };
|
|
1537
|
+
socket.on("tools.list", (_data, ack) => {
|
|
1538
|
+
ack?.(describeToolLibrary2(mergedTools));
|
|
1539
|
+
});
|
|
1540
|
+
socket.on("tools.get", (data, ack) => {
|
|
1541
|
+
const tool = mergedTools[data?.name];
|
|
1542
|
+
if (!tool) return ack?.({ error: `Tool "${data?.name}" not found` });
|
|
1543
|
+
ack?.({
|
|
1544
|
+
name: tool.name,
|
|
1545
|
+
description: tool.description,
|
|
1546
|
+
parameters: Object.keys(tool.parameters.shape ?? {})
|
|
1547
|
+
});
|
|
1548
|
+
});
|
|
1332
1549
|
socket.on("disconnect", () => {
|
|
1333
1550
|
});
|
|
1334
1551
|
socket.on("team.run", async (data) => {
|
|
1335
|
-
|
|
1552
|
+
if (!checkRate()) {
|
|
1553
|
+
socket.emit("agent.error", { error: "Rate limit exceeded" });
|
|
1554
|
+
return;
|
|
1555
|
+
}
|
|
1556
|
+
if (!data || typeof data.input !== "string" || !data.input.trim()) {
|
|
1557
|
+
socket.emit("agent.error", { error: "Invalid input: must be a non-empty string" });
|
|
1558
|
+
return;
|
|
1559
|
+
}
|
|
1560
|
+
if (data.sessionId !== void 0 && typeof data.sessionId !== "string") {
|
|
1561
|
+
socket.emit("agent.error", { error: "Invalid sessionId: must be a string" });
|
|
1562
|
+
return;
|
|
1563
|
+
}
|
|
1564
|
+
const team = opts.teams?.[data.name] ?? reg?.getTeam(data.name);
|
|
1336
1565
|
if (!team) {
|
|
1337
1566
|
socket.emit("agent.error", {
|
|
1338
1567
|
error: `Team "${data.name}" not found`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@radaros/transport",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.15",
|
|
4
4
|
"description": "HTTP and WebSocket transport layer for RadarOS agents",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"typescript": "^5.6.0"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
|
-
"@radaros/core": "^0.3.
|
|
42
|
+
"@radaros/core": "^0.3.15",
|
|
43
43
|
"@types/express": "^4.0.0 || ^5.0.0",
|
|
44
44
|
"express": "^4.0.0 || ^5.0.0",
|
|
45
45
|
"multer": ">=2.0.0",
|