@radaros/transport 0.3.10 → 0.3.13

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 ADDED
@@ -0,0 +1,45 @@
1
+ # @radaros/transport
2
+
3
+ HTTP and WebSocket transport layer for deploying RadarOS agents as APIs.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @radaros/transport
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```typescript
14
+ import express from "express";
15
+ import { Agent, openai } from "@radaros/core";
16
+ import { createAgentRouter } from "@radaros/transport";
17
+
18
+ const app = express();
19
+ app.use(express.json());
20
+
21
+ const agent = new Agent({
22
+ name: "assistant",
23
+ model: openai("gpt-4o"),
24
+ });
25
+
26
+ app.use("/api", createAgentRouter({ agents: { assistant: agent } }));
27
+ app.listen(3000);
28
+ ```
29
+
30
+ ## Features
31
+
32
+ - **Express Router** — REST API with streaming support
33
+ - **Socket.IO Gateway** — Real-time WebSocket communication
34
+ - **A2A Server** — Agent-to-Agent protocol support
35
+ - **CORS & Rate Limiting** — Built-in security middleware
36
+ - **Swagger** — Auto-generated API documentation
37
+ - **File Upload** — Multipart form data support
38
+
39
+ ## Documentation
40
+
41
+ Full docs at [radaros.mintlify.dev](https://radaros.mintlify.dev)
42
+
43
+ ## License
44
+
45
+ MIT
package/dist/index.d.ts CHANGED
@@ -42,8 +42,12 @@ interface FileUploadOptions {
42
42
  declare function createFileUploadMiddleware(opts?: FileUploadOptions): any;
43
43
  declare function buildMultiModalInput(body: any, files?: any[]): string | any[];
44
44
 
45
- declare function errorHandler(): (err: any, _req: any, res: any, _next: any) => void;
46
- declare function requestLogger(): (req: any, _res: any, next: any) => void;
45
+ declare function errorHandler(options?: {
46
+ logger?: Pick<Console, "error">;
47
+ }): (err: any, _req: any, res: any, _next: any) => void;
48
+ declare function requestLogger(options?: {
49
+ logger?: Pick<Console, "log">;
50
+ }): (req: any, _res: any, next: any) => void;
47
51
 
48
52
  interface SwaggerOptions {
49
53
  /** Enable Swagger UI at /docs. Default: false */
@@ -75,6 +79,13 @@ interface RouterOptions {
75
79
  swagger?: SwaggerOptions;
76
80
  /** File upload configuration for multi-modal inputs */
77
81
  fileUpload?: boolean | FileUploadOptions;
82
+ /** CORS configuration. Pass true or '*' for permissive, a string for a single origin, or an array for multiple origins. */
83
+ cors?: string | string[] | boolean;
84
+ /** Rate limiting configuration. Pass true for defaults (100 req/min), or an object to customize. */
85
+ rateLimit?: {
86
+ windowMs?: number;
87
+ max?: number;
88
+ } | boolean;
78
89
  }
79
90
 
80
91
  declare function createAgentRouter(opts: RouterOptions): any;
@@ -169,6 +180,8 @@ interface GatewayOptions {
169
180
  io: any;
170
181
  namespace?: string;
171
182
  authMiddleware?: (socket: any, next: (err?: Error) => void) => void;
183
+ /** Max requests per minute per socket. Default: 60 */
184
+ maxRequestsPerMinute?: number;
172
185
  }
173
186
 
174
187
  declare function createAgentGateway(opts: GatewayOptions): void;
package/dist/index.js CHANGED
@@ -64,10 +64,19 @@ function generateMultiAgentCard(agents, serverUrl, provider, version) {
64
64
  var _require = createRequire(import.meta.url);
65
65
  var TaskStore = class {
66
66
  tasks = /* @__PURE__ */ new Map();
67
+ maxTasks = 1e4;
67
68
  get(id) {
68
69
  return this.tasks.get(id);
69
70
  }
70
71
  set(task) {
72
+ if (this.tasks.size >= this.maxTasks) {
73
+ for (const [id, t] of this.tasks) {
74
+ if (t.status?.state === "completed" || t.status?.state === "canceled") {
75
+ this.tasks.delete(id);
76
+ break;
77
+ }
78
+ }
79
+ }
71
80
  this.tasks.set(task.id, task);
72
81
  }
73
82
  updateState(id, state, message) {
@@ -403,17 +412,19 @@ function buildMultiModalInput(body, files) {
403
412
  }
404
413
 
405
414
  // src/express/middleware.ts
406
- function errorHandler() {
415
+ function errorHandler(options) {
416
+ const log = options?.logger ?? console;
407
417
  return (err, _req, res, _next) => {
408
- console.error("[radaros:transport] Error:", err.message);
418
+ log.error("[radaros:transport] Error:", err.message);
409
419
  res.status(err.statusCode ?? 500).json({
410
420
  error: err.message ?? "Internal server error"
411
421
  });
412
422
  };
413
423
  }
414
- function requestLogger() {
424
+ function requestLogger(options) {
425
+ const log = options?.logger ?? console;
415
426
  return (req, _res, next) => {
416
- console.log(`[radaros:transport] ${req.method} ${req.path}`);
427
+ log.log(`[radaros:transport] ${req.method} ${req.path}`);
417
428
  next();
418
429
  };
419
430
  }
@@ -864,12 +875,76 @@ function serveSwaggerUI(spec) {
864
875
 
865
876
  // src/express/router-factory.ts
866
877
  var _require4 = createRequire4(import.meta.url);
878
+ function corsMiddleware(origins) {
879
+ return (req, res, next) => {
880
+ const origin = req.headers.origin;
881
+ let allowed = false;
882
+ if (origins === true || origins === "*") {
883
+ allowed = true;
884
+ res.setHeader("Access-Control-Allow-Origin", "*");
885
+ } else if (typeof origins === "string") {
886
+ allowed = origin === origins;
887
+ if (allowed) res.setHeader("Access-Control-Allow-Origin", origin);
888
+ } else if (Array.isArray(origins)) {
889
+ allowed = origins.includes(origin);
890
+ if (allowed) res.setHeader("Access-Control-Allow-Origin", origin);
891
+ }
892
+ if (allowed) {
893
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
894
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, X-API-Key");
895
+ res.setHeader("Access-Control-Max-Age", "86400");
896
+ }
897
+ if (req.method === "OPTIONS") {
898
+ res.status(204).end();
899
+ return;
900
+ }
901
+ next();
902
+ };
903
+ }
904
+ function rateLimitMiddleware(config = {}) {
905
+ const windowMs = config.windowMs ?? 6e4;
906
+ const max = config.max ?? 100;
907
+ const hits = /* @__PURE__ */ new Map();
908
+ return (req, res, next) => {
909
+ const key = req.ip ?? req.socket?.remoteAddress ?? "unknown";
910
+ const now = Date.now();
911
+ const record = hits.get(key);
912
+ if (!record || now > record.resetTime) {
913
+ hits.set(key, { count: 1, resetTime: now + windowMs });
914
+ next();
915
+ return;
916
+ }
917
+ record.count++;
918
+ if (record.count > max) {
919
+ res.status(429).json({ error: "Too many requests, please try again later" });
920
+ return;
921
+ }
922
+ next();
923
+ };
924
+ }
867
925
  var API_KEY_HEADERS = {
868
926
  "x-openai-api-key": "openai",
869
927
  "x-google-api-key": "google",
870
928
  "x-anthropic-api-key": "anthropic",
871
929
  "x-api-key": "_generic"
872
930
  };
931
+ function validateBody(body, fields) {
932
+ if (!body || typeof body !== "object") throw new Error("Invalid request body");
933
+ const result = {};
934
+ for (const [key, type] of Object.entries(fields)) {
935
+ const val = body[key];
936
+ const isOptional = type.endsWith("?");
937
+ const baseType = type.replace("?", "");
938
+ if (val === void 0 || val === null) {
939
+ if (!isOptional) throw new Error(`Missing required field: ${key}`);
940
+ continue;
941
+ }
942
+ if (baseType === "string" && typeof val !== "string") throw new Error(`Field ${key} must be a string`);
943
+ if (baseType === "object" && typeof val !== "object") throw new Error(`Field ${key} must be an object`);
944
+ result[key] = val;
945
+ }
946
+ return result;
947
+ }
873
948
  function extractApiKey(req, agent) {
874
949
  for (const [header, provider] of Object.entries(API_KEY_HEADERS)) {
875
950
  const value = req.headers[header];
@@ -887,6 +962,13 @@ function createAgentRouter(opts) {
887
962
  throw new Error("express is required for createAgentRouter. Install it: npm install express");
888
963
  }
889
964
  const router = express.Router();
965
+ if (opts.cors) {
966
+ router.use(corsMiddleware(opts.cors));
967
+ }
968
+ if (opts.rateLimit) {
969
+ const config = opts.rateLimit === true ? {} : opts.rateLimit;
970
+ router.use(rateLimitMiddleware(config));
971
+ }
890
972
  if (opts.middleware) {
891
973
  for (const mw of opts.middleware) {
892
974
  router.use(mw);
@@ -928,25 +1010,38 @@ function createAgentRouter(opts) {
928
1010
  `/agents/${name}/run`,
929
1011
  withUpload(async (req, res) => {
930
1012
  try {
931
- const input = buildMultiModalInput(req.body, req.files);
1013
+ const validated = validateBody(req.body, {
1014
+ input: "string",
1015
+ sessionId: "string?",
1016
+ userId: "string?"
1017
+ });
1018
+ const input = buildMultiModalInput(req.body, req.files) ?? validated.input;
932
1019
  if (!input) {
933
1020
  return res.status(400).json({ error: "input is required" });
934
1021
  }
935
- const { sessionId, userId } = req.body ?? {};
1022
+ const sessionId = validated.sessionId;
1023
+ const userId = validated.userId;
936
1024
  const apiKey = extractApiKey(req, agent);
937
1025
  const result = await agent.run(input, { sessionId, userId, apiKey });
938
1026
  res.json(result);
939
1027
  } catch (error) {
940
- res.status(500).json({ error: error.message });
1028
+ res.status(400).json({ error: error.message });
941
1029
  }
942
1030
  })
943
1031
  );
944
1032
  router.post(`/agents/${name}/stream`, async (req, res) => {
945
1033
  try {
946
- const { input, sessionId, userId } = req.body ?? {};
1034
+ const validated = validateBody(req.body, {
1035
+ input: "string",
1036
+ sessionId: "string?",
1037
+ userId: "string?"
1038
+ });
1039
+ const input = validated.input;
947
1040
  if (!input) {
948
1041
  return res.status(400).json({ error: "input is required" });
949
1042
  }
1043
+ const sessionId = validated.sessionId;
1044
+ const userId = validated.userId;
950
1045
  const apiKey = extractApiKey(req, agent);
951
1046
  res.writeHead(200, {
952
1047
  "Content-Type": "text/event-stream",
@@ -978,10 +1073,17 @@ function createAgentRouter(opts) {
978
1073
  for (const [name, team] of Object.entries(opts.teams)) {
979
1074
  router.post(`/teams/${name}/run`, async (req, res) => {
980
1075
  try {
981
- const { input, sessionId, userId } = req.body ?? {};
1076
+ const validated = validateBody(req.body, {
1077
+ input: "string",
1078
+ sessionId: "string?",
1079
+ userId: "string?"
1080
+ });
1081
+ const input = validated.input;
982
1082
  if (!input) {
983
1083
  return res.status(400).json({ error: "input is required" });
984
1084
  }
1085
+ const sessionId = validated.sessionId;
1086
+ const userId = validated.userId;
985
1087
  const apiKey = req.headers["x-api-key"] ?? req.body?.apiKey;
986
1088
  const result = await team.run(input, { sessionId, userId, apiKey });
987
1089
  res.json(result);
@@ -991,10 +1093,17 @@ function createAgentRouter(opts) {
991
1093
  });
992
1094
  router.post(`/teams/${name}/stream`, async (req, res) => {
993
1095
  try {
994
- const { input, sessionId, userId } = req.body ?? {};
1096
+ const validated = validateBody(req.body, {
1097
+ input: "string",
1098
+ sessionId: "string?",
1099
+ userId: "string?"
1100
+ });
1101
+ const input = validated.input;
995
1102
  if (!input) {
996
1103
  return res.status(400).json({ error: "input is required" });
997
1104
  }
1105
+ const sessionId = validated.sessionId;
1106
+ const userId = validated.userId;
998
1107
  const apiKey = req.headers["x-api-key"] ?? req.body?.apiKey;
999
1108
  res.writeHead(200, {
1000
1109
  "Content-Type": "text/event-stream",
@@ -1152,13 +1261,42 @@ function createBrowserGateway(opts) {
1152
1261
  }
1153
1262
 
1154
1263
  // src/socketio/gateway.ts
1264
+ function createSocketRateLimiter(maxPerMinute = 60) {
1265
+ return () => {
1266
+ let count = 0;
1267
+ let resetTime = Date.now() + 6e4;
1268
+ return () => {
1269
+ const now = Date.now();
1270
+ if (now > resetTime) {
1271
+ count = 0;
1272
+ resetTime = now + 6e4;
1273
+ }
1274
+ count++;
1275
+ return count <= maxPerMinute;
1276
+ };
1277
+ };
1278
+ }
1155
1279
  function createAgentGateway(opts) {
1156
1280
  const ns = opts.io.of(opts.namespace ?? "/radaros");
1157
1281
  if (opts.authMiddleware) {
1158
1282
  ns.use(opts.authMiddleware);
1159
1283
  }
1284
+ const rateLimiterFactory = createSocketRateLimiter(opts.maxRequestsPerMinute ?? 60);
1160
1285
  ns.on("connection", (socket) => {
1286
+ const checkRate = rateLimiterFactory();
1161
1287
  socket.on("agent.run", async (data) => {
1288
+ if (!checkRate()) {
1289
+ socket.emit("agent.error", { error: "Rate limit exceeded" });
1290
+ return;
1291
+ }
1292
+ if (!data || typeof data.input !== "string" || !data.input.trim()) {
1293
+ socket.emit("agent.error", { error: "Invalid input: must be a non-empty string" });
1294
+ return;
1295
+ }
1296
+ if (data.sessionId !== void 0 && typeof data.sessionId !== "string") {
1297
+ socket.emit("agent.error", { error: "Invalid sessionId: must be a string" });
1298
+ return;
1299
+ }
1162
1300
  const agent = opts.agents?.[data.name];
1163
1301
  if (!agent) {
1164
1302
  socket.emit("agent.error", {
@@ -1191,6 +1329,8 @@ function createAgentGateway(opts) {
1191
1329
  socket.emit("agent.error", { error: error.message });
1192
1330
  }
1193
1331
  });
1332
+ socket.on("disconnect", () => {
1333
+ });
1194
1334
  socket.on("team.run", async (data) => {
1195
1335
  const team = opts.teams?.[data.name];
1196
1336
  if (!team) {
@@ -1317,7 +1457,11 @@ function createVoiceGateway(opts) {
1317
1457
  if (session) {
1318
1458
  try {
1319
1459
  await session.close();
1320
- } catch {
1460
+ } catch (err) {
1461
+ console.warn(
1462
+ "[radaros/voice-gateway] Error closing session on disconnect:",
1463
+ err instanceof Error ? err.message : err
1464
+ );
1321
1465
  }
1322
1466
  activeSessions.delete(socket.id);
1323
1467
  }
package/package.json CHANGED
@@ -1,6 +1,21 @@
1
1
  {
2
2
  "name": "@radaros/transport",
3
- "version": "0.3.10",
3
+ "version": "0.3.13",
4
+ "description": "HTTP and WebSocket transport layer for RadarOS agents",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/xhipment/agentOs.git",
9
+ "directory": "packages/transport"
10
+ },
11
+ "keywords": [
12
+ "ai",
13
+ "agents",
14
+ "express",
15
+ "socketio",
16
+ "api",
17
+ "transport"
18
+ ],
4
19
  "type": "module",
5
20
  "main": "./dist/index.js",
6
21
  "types": "./dist/index.d.ts",
@@ -24,10 +39,10 @@
24
39
  "typescript": "^5.6.0"
25
40
  },
26
41
  "peerDependencies": {
27
- "@radaros/core": "^0.3.10",
42
+ "@radaros/core": "^0.3.13",
28
43
  "@types/express": "^4.0.0 || ^5.0.0",
29
44
  "express": "^4.0.0 || ^5.0.0",
30
- "multer": ">=1.4.0",
45
+ "multer": ">=2.0.0",
31
46
  "socket.io": "^4.0.0",
32
47
  "swagger-ui-express": "^5.0.0"
33
48
  },