@radaros/transport 0.3.13 → 0.3.14

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/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, 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>>;
@@ -175,6 +193,24 @@ interface BrowserGatewayOptions {
175
193
  declare function createBrowserGateway(opts: BrowserGatewayOptions): void;
176
194
 
177
195
  interface GatewayOptions {
196
+ /**
197
+ * Use a Registry for live auto-discovery. The gateway resolves agents/teams
198
+ * at event time — any instance created after the gateway starts is automatically
199
+ * reachable.
200
+ *
201
+ * When omitted, falls back to the global registry from `@radaros/core`.
202
+ * Pass `false` to disable registry-based lookup (use explicit maps only).
203
+ *
204
+ * @example
205
+ * createAgentGateway({ io });
206
+ * new Agent({ name: "bot", model: openai("gpt-4o") }); // immediately reachable
207
+ */
208
+ registry?: Registry | false;
209
+ /**
210
+ * Auto-discover agents and teams from a mixed array.
211
+ * Each item is classified by its `.kind` and keyed by `.name`.
212
+ */
213
+ serve?: Servable[];
178
214
  agents?: Record<string, Agent>;
179
215
  teams?: Record<string, Team>;
180
216
  io: any;
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, 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,138 @@ 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
+ }
1147
1290
  return router;
1148
1291
  }
1149
1292
 
@@ -1261,6 +1404,7 @@ function createBrowserGateway(opts) {
1261
1404
  }
1262
1405
 
1263
1406
  // src/socketio/gateway.ts
1407
+ import { classifyServables as classifyServables2, registry as globalRegistry2 } from "@radaros/core";
1264
1408
  function createSocketRateLimiter(maxPerMinute = 60) {
1265
1409
  return () => {
1266
1410
  let count = 0;
@@ -1277,6 +1421,15 @@ function createSocketRateLimiter(maxPerMinute = 60) {
1277
1421
  };
1278
1422
  }
1279
1423
  function createAgentGateway(opts) {
1424
+ if (opts.serve?.length) {
1425
+ const discovered = classifyServables2(opts.serve);
1426
+ opts = {
1427
+ ...opts,
1428
+ agents: { ...discovered.agents, ...opts.agents },
1429
+ teams: { ...discovered.teams, ...opts.teams }
1430
+ };
1431
+ }
1432
+ const reg = opts.registry === false ? null : opts.registry ?? globalRegistry2;
1280
1433
  const ns = opts.io.of(opts.namespace ?? "/radaros");
1281
1434
  if (opts.authMiddleware) {
1282
1435
  ns.use(opts.authMiddleware);
@@ -1297,7 +1450,7 @@ function createAgentGateway(opts) {
1297
1450
  socket.emit("agent.error", { error: "Invalid sessionId: must be a string" });
1298
1451
  return;
1299
1452
  }
1300
- const agent = opts.agents?.[data.name];
1453
+ const agent = opts.agents?.[data.name] ?? reg?.getAgent(data.name);
1301
1454
  if (!agent) {
1302
1455
  socket.emit("agent.error", {
1303
1456
  error: `Agent "${data.name}" not found`
@@ -1332,7 +1485,19 @@ function createAgentGateway(opts) {
1332
1485
  socket.on("disconnect", () => {
1333
1486
  });
1334
1487
  socket.on("team.run", async (data) => {
1335
- const team = opts.teams?.[data.name];
1488
+ if (!checkRate()) {
1489
+ socket.emit("agent.error", { error: "Rate limit exceeded" });
1490
+ return;
1491
+ }
1492
+ if (!data || typeof data.input !== "string" || !data.input.trim()) {
1493
+ socket.emit("agent.error", { error: "Invalid input: must be a non-empty string" });
1494
+ return;
1495
+ }
1496
+ if (data.sessionId !== void 0 && typeof data.sessionId !== "string") {
1497
+ socket.emit("agent.error", { error: "Invalid sessionId: must be a string" });
1498
+ return;
1499
+ }
1500
+ const team = opts.teams?.[data.name] ?? reg?.getTeam(data.name);
1336
1501
  if (!team) {
1337
1502
  socket.emit("agent.error", {
1338
1503
  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.13",
3
+ "version": "0.3.14",
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.13",
42
+ "@radaros/core": "^0.3.14",
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",