@radaros/transport 0.3.45 → 0.3.48

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.cjs CHANGED
@@ -28,6 +28,10 @@ __export(index_exports, {
28
28
  createAgentRouter: () => createAgentRouter,
29
29
  createBrowserGateway: () => createBrowserGateway,
30
30
  createFileUploadMiddleware: () => createFileUploadMiddleware,
31
+ createGatewayRouter: () => createGatewayRouter,
32
+ createJwtMiddleware: () => createJwtMiddleware,
33
+ createRbacMiddleware: () => createRbacMiddleware,
34
+ createVisionGateway: () => createVisionGateway,
31
35
  createVoiceGateway: () => createVoiceGateway,
32
36
  errorHandler: () => errorHandler,
33
37
  generateAgentCard: () => generateAgentCard,
@@ -721,6 +725,165 @@ function buildMultiModalInput(body, files) {
721
725
  return parts;
722
726
  }
723
727
 
728
+ // src/express/gateway.ts
729
+ var import_node_module4 = require("module");
730
+ var _require4 = (0, import_node_module4.createRequire)(importMetaUrl);
731
+ function createGatewayRouter(config) {
732
+ let express;
733
+ try {
734
+ express = _require4("express");
735
+ } catch {
736
+ throw new Error("express is required for gateway router. Install it: npm install express");
737
+ }
738
+ const router = express.Router();
739
+ const remoteHealth = /* @__PURE__ */ new Map();
740
+ async function checkHealth(remote) {
741
+ try {
742
+ const path = remote.healthPath ?? "/agents";
743
+ const res = await fetch(`${remote.baseUrl}${path}`, {
744
+ headers: remote.headers ?? {},
745
+ signal: AbortSignal.timeout(5e3)
746
+ });
747
+ return res.ok;
748
+ } catch {
749
+ return false;
750
+ }
751
+ }
752
+ if (config.healthCheckIntervalMs) {
753
+ const interval = setInterval(async () => {
754
+ for (const remote of config.remotes) {
755
+ const healthy = await checkHealth(remote);
756
+ remoteHealth.set(remote.baseUrl, healthy);
757
+ }
758
+ }, config.healthCheckIntervalMs);
759
+ interval.unref?.();
760
+ }
761
+ async function proxyRequest(baseUrl, path, req, res, headers) {
762
+ try {
763
+ const proxyRes = await fetch(`${baseUrl}${path}`, {
764
+ method: req.method,
765
+ headers: {
766
+ "Content-Type": "application/json",
767
+ ...headers ?? {},
768
+ ...Object.fromEntries(
769
+ Object.entries(req.headers).filter(([k]) => k.startsWith("x-") || k === "authorization")
770
+ )
771
+ },
772
+ body: req.method !== "GET" ? JSON.stringify(req.body) : void 0,
773
+ signal: AbortSignal.timeout(12e4)
774
+ });
775
+ const contentType = proxyRes.headers.get("content-type") ?? "";
776
+ if (contentType.includes("text/event-stream")) {
777
+ res.writeHead(200, {
778
+ "Content-Type": "text/event-stream",
779
+ "Cache-Control": "no-cache",
780
+ Connection: "keep-alive"
781
+ });
782
+ const reader = proxyRes.body?.getReader();
783
+ if (reader) {
784
+ const decoder = new TextDecoder();
785
+ while (true) {
786
+ const { done, value } = await reader.read();
787
+ if (done) break;
788
+ res.write(decoder.decode(value, { stream: true }));
789
+ }
790
+ }
791
+ res.end();
792
+ } else {
793
+ const data = await proxyRes.json();
794
+ res.status(proxyRes.status).json(data);
795
+ }
796
+ } catch (err) {
797
+ res.status(502).json({ error: `Gateway proxy error: ${err.message}` });
798
+ }
799
+ }
800
+ for (const remote of config.remotes) {
801
+ if (remote.agents) {
802
+ for (const agentName of remote.agents) {
803
+ router.post(
804
+ `/agents/${agentName}/run`,
805
+ (req, res) => proxyRequest(remote.baseUrl, `/agents/${agentName}/run`, req, res, remote.headers)
806
+ );
807
+ router.post(
808
+ `/agents/${agentName}/stream`,
809
+ (req, res) => proxyRequest(remote.baseUrl, `/agents/${agentName}/stream`, req, res, remote.headers)
810
+ );
811
+ }
812
+ }
813
+ if (remote.teams) {
814
+ for (const teamName of remote.teams) {
815
+ router.post(
816
+ `/teams/${teamName}/run`,
817
+ (req, res) => proxyRequest(remote.baseUrl, `/teams/${teamName}/run`, req, res, remote.headers)
818
+ );
819
+ router.post(
820
+ `/teams/${teamName}/stream`,
821
+ (req, res) => proxyRequest(remote.baseUrl, `/teams/${teamName}/stream`, req, res, remote.headers)
822
+ );
823
+ }
824
+ }
825
+ if (remote.workflows) {
826
+ for (const wfName of remote.workflows) {
827
+ router.post(
828
+ `/workflows/${wfName}/run`,
829
+ (req, res) => proxyRequest(remote.baseUrl, `/workflows/${wfName}/run`, req, res, remote.headers)
830
+ );
831
+ }
832
+ }
833
+ }
834
+ router.get("/gateway/health", async (_req, res) => {
835
+ const status = {};
836
+ for (const remote of config.remotes) {
837
+ const cached = remoteHealth.get(remote.baseUrl);
838
+ status[remote.baseUrl] = cached ?? await checkHealth(remote);
839
+ }
840
+ res.json({ remotes: status });
841
+ });
842
+ return router;
843
+ }
844
+
845
+ // src/express/jwt-middleware.ts
846
+ var import_node_module5 = require("module");
847
+ var _require5 = (0, import_node_module5.createRequire)(importMetaUrl);
848
+ function createJwtMiddleware(config) {
849
+ let jwt;
850
+ try {
851
+ jwt = _require5("jsonwebtoken");
852
+ } catch {
853
+ throw new Error("jsonwebtoken is required for JWT middleware. Install it: npm install jsonwebtoken");
854
+ }
855
+ const extractFrom = config.extractFrom ?? "header";
856
+ const cookieName = config.cookieName ?? "token";
857
+ return (req, res, next) => {
858
+ let token;
859
+ if (extractFrom === "header") {
860
+ const auth = req.headers.authorization;
861
+ if (auth?.startsWith("Bearer ")) {
862
+ token = auth.slice(7);
863
+ }
864
+ } else if (extractFrom === "cookie") {
865
+ token = req.cookies?.[cookieName];
866
+ }
867
+ if (!token) {
868
+ return res.status(401).json({ error: "Authentication required" });
869
+ }
870
+ try {
871
+ const verifyOpts = {};
872
+ if (config.algorithm) verifyOpts.algorithms = [config.algorithm];
873
+ if (config.issuer) verifyOpts.issuer = config.issuer;
874
+ if (config.audience) verifyOpts.audience = config.audience;
875
+ const decoded = jwt.verify(token, config.secret, verifyOpts);
876
+ req.user = decoded;
877
+ next();
878
+ } catch (err) {
879
+ if (err.name === "TokenExpiredError") {
880
+ return res.status(401).json({ error: "Token expired" });
881
+ }
882
+ return res.status(401).json({ error: "Invalid token" });
883
+ }
884
+ };
885
+ }
886
+
724
887
  // src/express/middleware.ts
725
888
  function errorHandler(options) {
726
889
  const log = options?.logger ?? console;
@@ -742,16 +905,81 @@ function requestLogger(options) {
742
905
  };
743
906
  }
744
907
 
908
+ // src/express/rbac-middleware.ts
909
+ var DEFAULT_SCOPE_MAP = {
910
+ "POST /agents/:name/run": ["agents:run"],
911
+ "POST /agents/:name/stream": ["agents:run"],
912
+ "GET /agents": ["agents:read"],
913
+ "POST /teams/:name/run": ["teams:run"],
914
+ "POST /teams/:name/stream": ["teams:run"],
915
+ "GET /teams": ["teams:read"],
916
+ "POST /workflows/:name/run": ["workflows:run"],
917
+ "GET /workflows": ["workflows:read"],
918
+ "GET /admin": ["admin:*"],
919
+ "POST /admin": ["admin:*"],
920
+ "DELETE /admin": ["admin:*"]
921
+ };
922
+ function normalizeRoute(method, path) {
923
+ const normalized = path.replace(/\/[a-zA-Z0-9_-]+(?=\/(?:run|stream|card|checkpoints))/g, "/:name");
924
+ return `${method} ${normalized}`;
925
+ }
926
+ function createRbacMiddleware(config = {}) {
927
+ const scopeField = config.scopeField ?? "scopes";
928
+ const scopeMap = { ...DEFAULT_SCOPE_MAP, ...config.defaultScopes ?? {} };
929
+ return (req, res, next) => {
930
+ const user = req.user;
931
+ if (!user) {
932
+ return res.status(401).json({ error: "Authentication required" });
933
+ }
934
+ const userScopes = user[scopeField] ?? user.scope?.split(" ") ?? [];
935
+ if (userScopes.includes("admin:*") || userScopes.includes("*")) {
936
+ return next();
937
+ }
938
+ const routeKey = normalizeRoute(req.method, req.path);
939
+ let requiredScopes = [];
940
+ for (const [pattern, scopes] of Object.entries(scopeMap)) {
941
+ if (routeKey === pattern || routeMatches(routeKey, pattern)) {
942
+ requiredScopes = scopes;
943
+ break;
944
+ }
945
+ }
946
+ if (config.agentScopes && req.params?.name) {
947
+ const agentSpecific = config.agentScopes[req.params.name];
948
+ if (agentSpecific) {
949
+ requiredScopes = [...requiredScopes, ...agentSpecific];
950
+ }
951
+ }
952
+ if (requiredScopes.length === 0) {
953
+ return next();
954
+ }
955
+ const hasRequired = requiredScopes.every((scope) => userScopes.includes(scope));
956
+ if (!hasRequired) {
957
+ return res.status(403).json({
958
+ error: "Insufficient permissions",
959
+ required: requiredScopes,
960
+ provided: userScopes
961
+ });
962
+ }
963
+ next();
964
+ };
965
+ }
966
+ function routeMatches(actual, pattern) {
967
+ const actualParts = actual.split(/[\s/]+/).filter(Boolean);
968
+ const patternParts = pattern.split(/[\s/]+/).filter(Boolean);
969
+ if (actualParts.length !== patternParts.length) return false;
970
+ return patternParts.every((part, i) => part.startsWith(":") || part === actualParts[i]);
971
+ }
972
+
745
973
  // src/express/router-factory.ts
746
- var import_node_module5 = require("module");
974
+ var import_node_module7 = require("module");
747
975
  var import_core3 = require("@radaros/core");
748
976
 
749
977
  // src/express/swagger.ts
750
- var import_node_module4 = require("module");
751
- var _require4 = (0, import_node_module4.createRequire)(importMetaUrl);
978
+ var import_node_module6 = require("module");
979
+ var _require6 = (0, import_node_module6.createRequire)(importMetaUrl);
752
980
  function zodSchemaToJsonSchema(schema) {
753
981
  try {
754
- const zodToJsonSchema = _require4("zod-to-json-schema").default ?? _require4("zod-to-json-schema");
982
+ const zodToJsonSchema = _require6("zod-to-json-schema").default ?? _require6("zod-to-json-schema");
755
983
  const result = zodToJsonSchema(schema, { target: "openApi3" });
756
984
  const { $schema, ...rest } = result;
757
985
  return rest;
@@ -1187,7 +1415,7 @@ ${agentDesc}`,
1187
1415
  function serveSwaggerUI(spec) {
1188
1416
  let swaggerUiExpress;
1189
1417
  try {
1190
- swaggerUiExpress = _require4("swagger-ui-express");
1418
+ swaggerUiExpress = _require6("swagger-ui-express");
1191
1419
  } catch {
1192
1420
  throw new Error("swagger-ui-express is required for Swagger UI. Install it: npm install swagger-ui-express");
1193
1421
  }
@@ -1201,7 +1429,7 @@ function serveSwaggerUI(spec) {
1201
1429
  }
1202
1430
 
1203
1431
  // src/express/router-factory.ts
1204
- var _require5 = (0, import_node_module5.createRequire)(importMetaUrl);
1432
+ var _require7 = (0, import_node_module7.createRequire)(importMetaUrl);
1205
1433
  function corsMiddleware(origins) {
1206
1434
  return (req, res, next) => {
1207
1435
  const origin = req.headers.origin;
@@ -1301,7 +1529,7 @@ function createAgentRouter(opts) {
1301
1529
  const reg = opts.registry === false ? null : opts.registry ?? import_core3.registry;
1302
1530
  let express;
1303
1531
  try {
1304
- express = _require5("express");
1532
+ express = _require7("express");
1305
1533
  } catch {
1306
1534
  throw new Error("express is required for createAgentRouter. Install it: npm install express");
1307
1535
  }
@@ -1313,6 +1541,12 @@ function createAgentRouter(opts) {
1313
1541
  const config = opts.rateLimit === true ? {} : opts.rateLimit;
1314
1542
  router.use(rateLimitMiddleware(config));
1315
1543
  }
1544
+ if (opts.jwt) {
1545
+ router.use(createJwtMiddleware(opts.jwt));
1546
+ }
1547
+ if (opts.rbac) {
1548
+ router.use(createRbacMiddleware(opts.rbac));
1549
+ }
1316
1550
  if (opts.middleware) {
1317
1551
  for (const mw of opts.middleware) {
1318
1552
  router.use(mw);
@@ -2080,6 +2314,128 @@ function createAgentGateway(opts) {
2080
2314
  });
2081
2315
  }
2082
2316
 
2317
+ // src/socketio/vision-gateway.ts
2318
+ function createVisionGateway(opts) {
2319
+ const ns = opts.io.of(opts.namespace ?? "/radaros-vision");
2320
+ if (opts.authMiddleware) {
2321
+ ns.use(opts.authMiddleware);
2322
+ }
2323
+ const activeSessions = /* @__PURE__ */ new Map();
2324
+ ns.on("connection", (socket) => {
2325
+ socket.on(
2326
+ "vision.start",
2327
+ async (data) => {
2328
+ const agent = opts.agents[data.agentName];
2329
+ if (!agent) {
2330
+ socket.emit("vision.error", { error: `Vision agent "${data.agentName}" not found` });
2331
+ return;
2332
+ }
2333
+ if (activeSessions.has(socket.id)) {
2334
+ socket.emit("vision.error", { error: "A vision session is already active for this connection" });
2335
+ return;
2336
+ }
2337
+ try {
2338
+ const apiKey = data.apiKey ?? socket.handshake?.auth?.apiKey;
2339
+ const userId = data.userId ?? socket.handshake?.auth?.userId;
2340
+ const sessionId = data.sessionId ?? socket.handshake?.auth?.sessionId;
2341
+ const session = await agent.connect({ apiKey, userId, sessionId });
2342
+ activeSessions.set(socket.id, session);
2343
+ session.on("audio", (ev) => {
2344
+ socket.emit("vision.audio", {
2345
+ data: ev.data.toString("base64"),
2346
+ mimeType: ev.mimeType ?? "audio/pcm"
2347
+ });
2348
+ });
2349
+ session.on("transcript", (ev) => {
2350
+ socket.emit("vision.transcript", { text: ev.text, role: ev.role });
2351
+ });
2352
+ session.on("text", (ev) => {
2353
+ socket.emit("vision.text", { text: ev.text });
2354
+ });
2355
+ session.on("tool_call_start", (ev) => {
2356
+ socket.emit("vision.tool.call", { name: ev.name, args: ev.args });
2357
+ });
2358
+ session.on("tool_result", (ev) => {
2359
+ socket.emit("vision.tool.result", { name: ev.name, result: ev.result });
2360
+ });
2361
+ session.on("usage", (ev) => {
2362
+ socket.emit("vision.usage", ev);
2363
+ });
2364
+ session.on("interrupted", () => {
2365
+ socket.emit("vision.interrupted");
2366
+ });
2367
+ session.on("error", (ev) => {
2368
+ socket.emit("vision.error", { error: ev.error.message });
2369
+ });
2370
+ session.on("disconnected", () => {
2371
+ activeSessions.delete(socket.id);
2372
+ socket.emit("vision.stopped");
2373
+ });
2374
+ socket.emit("vision.started", { userId });
2375
+ } catch (error) {
2376
+ socket.emit("vision.error", { error: error.message });
2377
+ }
2378
+ }
2379
+ );
2380
+ socket.on("vision.audio", (data) => {
2381
+ const session = activeSessions.get(socket.id);
2382
+ if (!session) return;
2383
+ if (typeof data?.data !== "string" || data.data.length > 1e6) return;
2384
+ try {
2385
+ session.sendAudio(Buffer.from(data.data, "base64"));
2386
+ } catch {
2387
+ socket.emit("vision.error", { error: "Invalid audio data" });
2388
+ }
2389
+ });
2390
+ socket.on("vision.image", (data) => {
2391
+ const session = activeSessions.get(socket.id);
2392
+ if (!session) return;
2393
+ if (typeof data?.data !== "string" || data.data.length > 5e6) return;
2394
+ try {
2395
+ session.sendImage(Buffer.from(data.data, "base64"), data.mimeType ?? "image/jpeg");
2396
+ } catch {
2397
+ socket.emit("vision.error", { error: "Invalid image data" });
2398
+ }
2399
+ });
2400
+ socket.on("vision.text", (data) => {
2401
+ const session = activeSessions.get(socket.id);
2402
+ if (!session) return;
2403
+ if (typeof data?.text !== "string" || data.text.length > 1e4) return;
2404
+ session.sendText(data.text);
2405
+ });
2406
+ socket.on("vision.interrupt", () => {
2407
+ const session = activeSessions.get(socket.id);
2408
+ if (!session) return;
2409
+ session.interrupt();
2410
+ });
2411
+ socket.on("vision.stop", async () => {
2412
+ const session = activeSessions.get(socket.id);
2413
+ if (!session) return;
2414
+ try {
2415
+ await session.close();
2416
+ } catch (err) {
2417
+ console.warn("[vision-gateway] Error closing session:", err);
2418
+ }
2419
+ activeSessions.delete(socket.id);
2420
+ socket.emit("vision.stopped");
2421
+ });
2422
+ socket.on("disconnect", async () => {
2423
+ const session = activeSessions.get(socket.id);
2424
+ if (session) {
2425
+ try {
2426
+ await session.close();
2427
+ } catch (err) {
2428
+ console.warn(
2429
+ "[radaros/vision-gateway] Error closing session on disconnect:",
2430
+ err instanceof Error ? err.message : err
2431
+ );
2432
+ }
2433
+ activeSessions.delete(socket.id);
2434
+ }
2435
+ });
2436
+ });
2437
+ }
2438
+
2083
2439
  // src/socketio/voice-gateway.ts
2084
2440
  function createVoiceGateway(opts) {
2085
2441
  const ns = opts.io.of(opts.namespace ?? "/radaros-voice");
@@ -2221,6 +2577,10 @@ function createVoiceGateway(opts) {
2221
2577
  createAgentRouter,
2222
2578
  createBrowserGateway,
2223
2579
  createFileUploadMiddleware,
2580
+ createGatewayRouter,
2581
+ createJwtMiddleware,
2582
+ createRbacMiddleware,
2583
+ createVisionGateway,
2224
2584
  createVoiceGateway,
2225
2585
  errorHandler,
2226
2586
  generateAgentCard,
package/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { Agent, A2AAgentCard, MCPToolProviderConfig, ToolDef, MCPToolProvider, Registry, Servable, Team, Workflow, Toolkit, EventBus, VoiceAgent } from '@radaros/core';
1
+ import { Agent, A2AAgentCard, MCPToolProviderConfig, ToolDef, MCPToolProvider, Registry, Servable, Team, Workflow, Toolkit, EventBus, VisionAgent, VoiceAgent } from '@radaros/core';
2
2
 
3
3
  interface A2AServerOptions {
4
4
  agents: Record<string, Agent>;
@@ -127,12 +127,22 @@ interface FileUploadOptions {
127
127
  declare function createFileUploadMiddleware(opts?: FileUploadOptions): any;
128
128
  declare function buildMultiModalInput(body: any, files?: any[]): string | any[];
129
129
 
130
- declare function errorHandler(options?: {
131
- logger?: Pick<Console, "error">;
132
- }): (err: any, _req: any, res: any, _next: any) => void;
133
- declare function requestLogger(options?: {
134
- logger?: Pick<Console, "log">;
135
- }): (req: any, _res: any, next: any) => void;
130
+ interface RbacConfig {
131
+ scopeField?: string;
132
+ defaultScopes?: Record<string, string[]>;
133
+ agentScopes?: Record<string, string[]>;
134
+ }
135
+ declare function createRbacMiddleware(config?: RbacConfig): (req: any, res: any, next: any) => any;
136
+
137
+ interface JwtConfig {
138
+ secret: string;
139
+ algorithm?: string;
140
+ issuer?: string;
141
+ audience?: string;
142
+ extractFrom?: "header" | "cookie";
143
+ cookieName?: string;
144
+ }
145
+ declare function createJwtMiddleware(config: JwtConfig): (req: any, res: any, next: any) => any;
136
146
 
137
147
  interface SwaggerOptions {
138
148
  /** Enable Swagger UI at /docs. Default: false */
@@ -210,7 +220,39 @@ interface RouterOptions {
210
220
  * MetricsExporter instance from `@radaros/observability` for `/metrics` endpoints.
211
221
  */
212
222
  metricsExporter?: any;
223
+ /**
224
+ * JWT authentication middleware. Verifies tokens and attaches decoded payload to `req.user`.
225
+ * Requires `jsonwebtoken` package.
226
+ */
227
+ jwt?: JwtConfig;
228
+ /**
229
+ * Role-based access control. Checks `req.user.scopes` against required scopes per route.
230
+ * Requires `jwt` to be configured first.
231
+ */
232
+ rbac?: RbacConfig;
233
+ }
234
+
235
+ interface RemoteEndpoint {
236
+ baseUrl: string;
237
+ agents?: string[];
238
+ teams?: string[];
239
+ workflows?: string[];
240
+ headers?: Record<string, string>;
241
+ healthPath?: string;
242
+ }
243
+ interface GatewayConfig {
244
+ locals?: RouterOptions;
245
+ remotes: RemoteEndpoint[];
246
+ healthCheckIntervalMs?: number;
213
247
  }
248
+ declare function createGatewayRouter(config: GatewayConfig): any;
249
+
250
+ declare function errorHandler(options?: {
251
+ logger?: Pick<Console, "error">;
252
+ }): (err: any, _req: any, res: any, _next: any) => void;
253
+ declare function requestLogger(options?: {
254
+ logger?: Pick<Console, "log">;
255
+ }): (req: any, _res: any, next: any) => void;
214
256
 
215
257
  declare function createAgentRouter(opts: RouterOptions): any;
216
258
 
@@ -332,6 +374,14 @@ interface GatewayOptions {
332
374
 
333
375
  declare function createAgentGateway(opts: GatewayOptions): void;
334
376
 
377
+ interface VisionGatewayOptions {
378
+ agents: Record<string, VisionAgent>;
379
+ io: any;
380
+ namespace?: string;
381
+ authMiddleware?: (socket: any, next: (err?: Error) => void) => void;
382
+ }
383
+ declare function createVisionGateway(opts: VisionGatewayOptions): void;
384
+
335
385
  interface VoiceGatewayOptions {
336
386
  agents: Record<string, VoiceAgent>;
337
387
  io: any;
@@ -340,4 +390,4 @@ interface VoiceGatewayOptions {
340
390
  }
341
391
  declare function createVoiceGateway(opts: VoiceGatewayOptions): void;
342
392
 
343
- export { type A2AServerOptions, type AdminRouterOptions, type BrowserGatewayOptions, type FileUploadOptions, type GatewayOptions, MCPManager, type MCPServerEntry, type MCPServerSummary, type RouterOptions, type SwaggerOptions, type VoiceGatewayOptions, buildMultiModalInput, createA2AServer, createAdminRouter, createAgentGateway, createAgentRouter, createBrowserGateway, createFileUploadMiddleware, createVoiceGateway, errorHandler, generateAgentCard, generateMultiAgentCard, generateOpenAPISpec, requestLogger };
393
+ export { type A2AServerOptions, type AdminRouterOptions, type BrowserGatewayOptions, type FileUploadOptions, type GatewayConfig, type GatewayOptions, type JwtConfig, MCPManager, type MCPServerEntry, type MCPServerSummary, type RbacConfig, type RemoteEndpoint, type RouterOptions, type SwaggerOptions, type VisionGatewayOptions, type VoiceGatewayOptions, buildMultiModalInput, createA2AServer, createAdminRouter, createAgentGateway, createAgentRouter, createBrowserGateway, createFileUploadMiddleware, createGatewayRouter, createJwtMiddleware, createRbacMiddleware, createVisionGateway, createVoiceGateway, errorHandler, generateAgentCard, generateMultiAgentCard, generateOpenAPISpec, requestLogger };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Agent, A2AAgentCard, MCPToolProviderConfig, ToolDef, MCPToolProvider, Registry, Servable, Team, Workflow, Toolkit, EventBus, VoiceAgent } from '@radaros/core';
1
+ import { Agent, A2AAgentCard, MCPToolProviderConfig, ToolDef, MCPToolProvider, Registry, Servable, Team, Workflow, Toolkit, EventBus, VisionAgent, VoiceAgent } from '@radaros/core';
2
2
 
3
3
  interface A2AServerOptions {
4
4
  agents: Record<string, Agent>;
@@ -127,12 +127,22 @@ interface FileUploadOptions {
127
127
  declare function createFileUploadMiddleware(opts?: FileUploadOptions): any;
128
128
  declare function buildMultiModalInput(body: any, files?: any[]): string | any[];
129
129
 
130
- declare function errorHandler(options?: {
131
- logger?: Pick<Console, "error">;
132
- }): (err: any, _req: any, res: any, _next: any) => void;
133
- declare function requestLogger(options?: {
134
- logger?: Pick<Console, "log">;
135
- }): (req: any, _res: any, next: any) => void;
130
+ interface RbacConfig {
131
+ scopeField?: string;
132
+ defaultScopes?: Record<string, string[]>;
133
+ agentScopes?: Record<string, string[]>;
134
+ }
135
+ declare function createRbacMiddleware(config?: RbacConfig): (req: any, res: any, next: any) => any;
136
+
137
+ interface JwtConfig {
138
+ secret: string;
139
+ algorithm?: string;
140
+ issuer?: string;
141
+ audience?: string;
142
+ extractFrom?: "header" | "cookie";
143
+ cookieName?: string;
144
+ }
145
+ declare function createJwtMiddleware(config: JwtConfig): (req: any, res: any, next: any) => any;
136
146
 
137
147
  interface SwaggerOptions {
138
148
  /** Enable Swagger UI at /docs. Default: false */
@@ -210,7 +220,39 @@ interface RouterOptions {
210
220
  * MetricsExporter instance from `@radaros/observability` for `/metrics` endpoints.
211
221
  */
212
222
  metricsExporter?: any;
223
+ /**
224
+ * JWT authentication middleware. Verifies tokens and attaches decoded payload to `req.user`.
225
+ * Requires `jsonwebtoken` package.
226
+ */
227
+ jwt?: JwtConfig;
228
+ /**
229
+ * Role-based access control. Checks `req.user.scopes` against required scopes per route.
230
+ * Requires `jwt` to be configured first.
231
+ */
232
+ rbac?: RbacConfig;
233
+ }
234
+
235
+ interface RemoteEndpoint {
236
+ baseUrl: string;
237
+ agents?: string[];
238
+ teams?: string[];
239
+ workflows?: string[];
240
+ headers?: Record<string, string>;
241
+ healthPath?: string;
242
+ }
243
+ interface GatewayConfig {
244
+ locals?: RouterOptions;
245
+ remotes: RemoteEndpoint[];
246
+ healthCheckIntervalMs?: number;
213
247
  }
248
+ declare function createGatewayRouter(config: GatewayConfig): any;
249
+
250
+ declare function errorHandler(options?: {
251
+ logger?: Pick<Console, "error">;
252
+ }): (err: any, _req: any, res: any, _next: any) => void;
253
+ declare function requestLogger(options?: {
254
+ logger?: Pick<Console, "log">;
255
+ }): (req: any, _res: any, next: any) => void;
214
256
 
215
257
  declare function createAgentRouter(opts: RouterOptions): any;
216
258
 
@@ -332,6 +374,14 @@ interface GatewayOptions {
332
374
 
333
375
  declare function createAgentGateway(opts: GatewayOptions): void;
334
376
 
377
+ interface VisionGatewayOptions {
378
+ agents: Record<string, VisionAgent>;
379
+ io: any;
380
+ namespace?: string;
381
+ authMiddleware?: (socket: any, next: (err?: Error) => void) => void;
382
+ }
383
+ declare function createVisionGateway(opts: VisionGatewayOptions): void;
384
+
335
385
  interface VoiceGatewayOptions {
336
386
  agents: Record<string, VoiceAgent>;
337
387
  io: any;
@@ -340,4 +390,4 @@ interface VoiceGatewayOptions {
340
390
  }
341
391
  declare function createVoiceGateway(opts: VoiceGatewayOptions): void;
342
392
 
343
- export { type A2AServerOptions, type AdminRouterOptions, type BrowserGatewayOptions, type FileUploadOptions, type GatewayOptions, MCPManager, type MCPServerEntry, type MCPServerSummary, type RouterOptions, type SwaggerOptions, type VoiceGatewayOptions, buildMultiModalInput, createA2AServer, createAdminRouter, createAgentGateway, createAgentRouter, createBrowserGateway, createFileUploadMiddleware, createVoiceGateway, errorHandler, generateAgentCard, generateMultiAgentCard, generateOpenAPISpec, requestLogger };
393
+ export { type A2AServerOptions, type AdminRouterOptions, type BrowserGatewayOptions, type FileUploadOptions, type GatewayConfig, type GatewayOptions, type JwtConfig, MCPManager, type MCPServerEntry, type MCPServerSummary, type RbacConfig, type RemoteEndpoint, type RouterOptions, type SwaggerOptions, type VisionGatewayOptions, type VoiceGatewayOptions, buildMultiModalInput, createA2AServer, createAdminRouter, createAgentGateway, createAgentRouter, createBrowserGateway, createFileUploadMiddleware, createGatewayRouter, createJwtMiddleware, createRbacMiddleware, createVisionGateway, createVoiceGateway, errorHandler, generateAgentCard, generateMultiAgentCard, generateOpenAPISpec, requestLogger };
package/dist/index.js CHANGED
@@ -678,6 +678,165 @@ function buildMultiModalInput(body, files) {
678
678
  return parts;
679
679
  }
680
680
 
681
+ // src/express/gateway.ts
682
+ import { createRequire as createRequire4 } from "module";
683
+ var _require4 = createRequire4(import.meta.url);
684
+ function createGatewayRouter(config) {
685
+ let express;
686
+ try {
687
+ express = _require4("express");
688
+ } catch {
689
+ throw new Error("express is required for gateway router. Install it: npm install express");
690
+ }
691
+ const router = express.Router();
692
+ const remoteHealth = /* @__PURE__ */ new Map();
693
+ async function checkHealth(remote) {
694
+ try {
695
+ const path = remote.healthPath ?? "/agents";
696
+ const res = await fetch(`${remote.baseUrl}${path}`, {
697
+ headers: remote.headers ?? {},
698
+ signal: AbortSignal.timeout(5e3)
699
+ });
700
+ return res.ok;
701
+ } catch {
702
+ return false;
703
+ }
704
+ }
705
+ if (config.healthCheckIntervalMs) {
706
+ const interval = setInterval(async () => {
707
+ for (const remote of config.remotes) {
708
+ const healthy = await checkHealth(remote);
709
+ remoteHealth.set(remote.baseUrl, healthy);
710
+ }
711
+ }, config.healthCheckIntervalMs);
712
+ interval.unref?.();
713
+ }
714
+ async function proxyRequest(baseUrl, path, req, res, headers) {
715
+ try {
716
+ const proxyRes = await fetch(`${baseUrl}${path}`, {
717
+ method: req.method,
718
+ headers: {
719
+ "Content-Type": "application/json",
720
+ ...headers ?? {},
721
+ ...Object.fromEntries(
722
+ Object.entries(req.headers).filter(([k]) => k.startsWith("x-") || k === "authorization")
723
+ )
724
+ },
725
+ body: req.method !== "GET" ? JSON.stringify(req.body) : void 0,
726
+ signal: AbortSignal.timeout(12e4)
727
+ });
728
+ const contentType = proxyRes.headers.get("content-type") ?? "";
729
+ if (contentType.includes("text/event-stream")) {
730
+ res.writeHead(200, {
731
+ "Content-Type": "text/event-stream",
732
+ "Cache-Control": "no-cache",
733
+ Connection: "keep-alive"
734
+ });
735
+ const reader = proxyRes.body?.getReader();
736
+ if (reader) {
737
+ const decoder = new TextDecoder();
738
+ while (true) {
739
+ const { done, value } = await reader.read();
740
+ if (done) break;
741
+ res.write(decoder.decode(value, { stream: true }));
742
+ }
743
+ }
744
+ res.end();
745
+ } else {
746
+ const data = await proxyRes.json();
747
+ res.status(proxyRes.status).json(data);
748
+ }
749
+ } catch (err) {
750
+ res.status(502).json({ error: `Gateway proxy error: ${err.message}` });
751
+ }
752
+ }
753
+ for (const remote of config.remotes) {
754
+ if (remote.agents) {
755
+ for (const agentName of remote.agents) {
756
+ router.post(
757
+ `/agents/${agentName}/run`,
758
+ (req, res) => proxyRequest(remote.baseUrl, `/agents/${agentName}/run`, req, res, remote.headers)
759
+ );
760
+ router.post(
761
+ `/agents/${agentName}/stream`,
762
+ (req, res) => proxyRequest(remote.baseUrl, `/agents/${agentName}/stream`, req, res, remote.headers)
763
+ );
764
+ }
765
+ }
766
+ if (remote.teams) {
767
+ for (const teamName of remote.teams) {
768
+ router.post(
769
+ `/teams/${teamName}/run`,
770
+ (req, res) => proxyRequest(remote.baseUrl, `/teams/${teamName}/run`, req, res, remote.headers)
771
+ );
772
+ router.post(
773
+ `/teams/${teamName}/stream`,
774
+ (req, res) => proxyRequest(remote.baseUrl, `/teams/${teamName}/stream`, req, res, remote.headers)
775
+ );
776
+ }
777
+ }
778
+ if (remote.workflows) {
779
+ for (const wfName of remote.workflows) {
780
+ router.post(
781
+ `/workflows/${wfName}/run`,
782
+ (req, res) => proxyRequest(remote.baseUrl, `/workflows/${wfName}/run`, req, res, remote.headers)
783
+ );
784
+ }
785
+ }
786
+ }
787
+ router.get("/gateway/health", async (_req, res) => {
788
+ const status = {};
789
+ for (const remote of config.remotes) {
790
+ const cached = remoteHealth.get(remote.baseUrl);
791
+ status[remote.baseUrl] = cached ?? await checkHealth(remote);
792
+ }
793
+ res.json({ remotes: status });
794
+ });
795
+ return router;
796
+ }
797
+
798
+ // src/express/jwt-middleware.ts
799
+ import { createRequire as createRequire5 } from "module";
800
+ var _require5 = createRequire5(import.meta.url);
801
+ function createJwtMiddleware(config) {
802
+ let jwt;
803
+ try {
804
+ jwt = _require5("jsonwebtoken");
805
+ } catch {
806
+ throw new Error("jsonwebtoken is required for JWT middleware. Install it: npm install jsonwebtoken");
807
+ }
808
+ const extractFrom = config.extractFrom ?? "header";
809
+ const cookieName = config.cookieName ?? "token";
810
+ return (req, res, next) => {
811
+ let token;
812
+ if (extractFrom === "header") {
813
+ const auth = req.headers.authorization;
814
+ if (auth?.startsWith("Bearer ")) {
815
+ token = auth.slice(7);
816
+ }
817
+ } else if (extractFrom === "cookie") {
818
+ token = req.cookies?.[cookieName];
819
+ }
820
+ if (!token) {
821
+ return res.status(401).json({ error: "Authentication required" });
822
+ }
823
+ try {
824
+ const verifyOpts = {};
825
+ if (config.algorithm) verifyOpts.algorithms = [config.algorithm];
826
+ if (config.issuer) verifyOpts.issuer = config.issuer;
827
+ if (config.audience) verifyOpts.audience = config.audience;
828
+ const decoded = jwt.verify(token, config.secret, verifyOpts);
829
+ req.user = decoded;
830
+ next();
831
+ } catch (err) {
832
+ if (err.name === "TokenExpiredError") {
833
+ return res.status(401).json({ error: "Token expired" });
834
+ }
835
+ return res.status(401).json({ error: "Invalid token" });
836
+ }
837
+ };
838
+ }
839
+
681
840
  // src/express/middleware.ts
682
841
  function errorHandler(options) {
683
842
  const log = options?.logger ?? console;
@@ -699,16 +858,81 @@ function requestLogger(options) {
699
858
  };
700
859
  }
701
860
 
861
+ // src/express/rbac-middleware.ts
862
+ var DEFAULT_SCOPE_MAP = {
863
+ "POST /agents/:name/run": ["agents:run"],
864
+ "POST /agents/:name/stream": ["agents:run"],
865
+ "GET /agents": ["agents:read"],
866
+ "POST /teams/:name/run": ["teams:run"],
867
+ "POST /teams/:name/stream": ["teams:run"],
868
+ "GET /teams": ["teams:read"],
869
+ "POST /workflows/:name/run": ["workflows:run"],
870
+ "GET /workflows": ["workflows:read"],
871
+ "GET /admin": ["admin:*"],
872
+ "POST /admin": ["admin:*"],
873
+ "DELETE /admin": ["admin:*"]
874
+ };
875
+ function normalizeRoute(method, path) {
876
+ const normalized = path.replace(/\/[a-zA-Z0-9_-]+(?=\/(?:run|stream|card|checkpoints))/g, "/:name");
877
+ return `${method} ${normalized}`;
878
+ }
879
+ function createRbacMiddleware(config = {}) {
880
+ const scopeField = config.scopeField ?? "scopes";
881
+ const scopeMap = { ...DEFAULT_SCOPE_MAP, ...config.defaultScopes ?? {} };
882
+ return (req, res, next) => {
883
+ const user = req.user;
884
+ if (!user) {
885
+ return res.status(401).json({ error: "Authentication required" });
886
+ }
887
+ const userScopes = user[scopeField] ?? user.scope?.split(" ") ?? [];
888
+ if (userScopes.includes("admin:*") || userScopes.includes("*")) {
889
+ return next();
890
+ }
891
+ const routeKey = normalizeRoute(req.method, req.path);
892
+ let requiredScopes = [];
893
+ for (const [pattern, scopes] of Object.entries(scopeMap)) {
894
+ if (routeKey === pattern || routeMatches(routeKey, pattern)) {
895
+ requiredScopes = scopes;
896
+ break;
897
+ }
898
+ }
899
+ if (config.agentScopes && req.params?.name) {
900
+ const agentSpecific = config.agentScopes[req.params.name];
901
+ if (agentSpecific) {
902
+ requiredScopes = [...requiredScopes, ...agentSpecific];
903
+ }
904
+ }
905
+ if (requiredScopes.length === 0) {
906
+ return next();
907
+ }
908
+ const hasRequired = requiredScopes.every((scope) => userScopes.includes(scope));
909
+ if (!hasRequired) {
910
+ return res.status(403).json({
911
+ error: "Insufficient permissions",
912
+ required: requiredScopes,
913
+ provided: userScopes
914
+ });
915
+ }
916
+ next();
917
+ };
918
+ }
919
+ function routeMatches(actual, pattern) {
920
+ const actualParts = actual.split(/[\s/]+/).filter(Boolean);
921
+ const patternParts = pattern.split(/[\s/]+/).filter(Boolean);
922
+ if (actualParts.length !== patternParts.length) return false;
923
+ return patternParts.every((part, i) => part.startsWith(":") || part === actualParts[i]);
924
+ }
925
+
702
926
  // src/express/router-factory.ts
703
- import { createRequire as createRequire5 } from "module";
927
+ import { createRequire as createRequire7 } from "module";
704
928
  import { classifyServables, collectToolkitTools, describeToolLibrary, registry as globalRegistry } from "@radaros/core";
705
929
 
706
930
  // src/express/swagger.ts
707
- import { createRequire as createRequire4 } from "module";
708
- var _require4 = createRequire4(import.meta.url);
931
+ import { createRequire as createRequire6 } from "module";
932
+ var _require6 = createRequire6(import.meta.url);
709
933
  function zodSchemaToJsonSchema(schema) {
710
934
  try {
711
- const zodToJsonSchema = _require4("zod-to-json-schema").default ?? _require4("zod-to-json-schema");
935
+ const zodToJsonSchema = _require6("zod-to-json-schema").default ?? _require6("zod-to-json-schema");
712
936
  const result = zodToJsonSchema(schema, { target: "openApi3" });
713
937
  const { $schema, ...rest } = result;
714
938
  return rest;
@@ -1144,7 +1368,7 @@ ${agentDesc}`,
1144
1368
  function serveSwaggerUI(spec) {
1145
1369
  let swaggerUiExpress;
1146
1370
  try {
1147
- swaggerUiExpress = _require4("swagger-ui-express");
1371
+ swaggerUiExpress = _require6("swagger-ui-express");
1148
1372
  } catch {
1149
1373
  throw new Error("swagger-ui-express is required for Swagger UI. Install it: npm install swagger-ui-express");
1150
1374
  }
@@ -1158,7 +1382,7 @@ function serveSwaggerUI(spec) {
1158
1382
  }
1159
1383
 
1160
1384
  // src/express/router-factory.ts
1161
- var _require5 = createRequire5(import.meta.url);
1385
+ var _require7 = createRequire7(import.meta.url);
1162
1386
  function corsMiddleware(origins) {
1163
1387
  return (req, res, next) => {
1164
1388
  const origin = req.headers.origin;
@@ -1258,7 +1482,7 @@ function createAgentRouter(opts) {
1258
1482
  const reg = opts.registry === false ? null : opts.registry ?? globalRegistry;
1259
1483
  let express;
1260
1484
  try {
1261
- express = _require5("express");
1485
+ express = _require7("express");
1262
1486
  } catch {
1263
1487
  throw new Error("express is required for createAgentRouter. Install it: npm install express");
1264
1488
  }
@@ -1270,6 +1494,12 @@ function createAgentRouter(opts) {
1270
1494
  const config = opts.rateLimit === true ? {} : opts.rateLimit;
1271
1495
  router.use(rateLimitMiddleware(config));
1272
1496
  }
1497
+ if (opts.jwt) {
1498
+ router.use(createJwtMiddleware(opts.jwt));
1499
+ }
1500
+ if (opts.rbac) {
1501
+ router.use(createRbacMiddleware(opts.rbac));
1502
+ }
1273
1503
  if (opts.middleware) {
1274
1504
  for (const mw of opts.middleware) {
1275
1505
  router.use(mw);
@@ -2037,6 +2267,128 @@ function createAgentGateway(opts) {
2037
2267
  });
2038
2268
  }
2039
2269
 
2270
+ // src/socketio/vision-gateway.ts
2271
+ function createVisionGateway(opts) {
2272
+ const ns = opts.io.of(opts.namespace ?? "/radaros-vision");
2273
+ if (opts.authMiddleware) {
2274
+ ns.use(opts.authMiddleware);
2275
+ }
2276
+ const activeSessions = /* @__PURE__ */ new Map();
2277
+ ns.on("connection", (socket) => {
2278
+ socket.on(
2279
+ "vision.start",
2280
+ async (data) => {
2281
+ const agent = opts.agents[data.agentName];
2282
+ if (!agent) {
2283
+ socket.emit("vision.error", { error: `Vision agent "${data.agentName}" not found` });
2284
+ return;
2285
+ }
2286
+ if (activeSessions.has(socket.id)) {
2287
+ socket.emit("vision.error", { error: "A vision session is already active for this connection" });
2288
+ return;
2289
+ }
2290
+ try {
2291
+ const apiKey = data.apiKey ?? socket.handshake?.auth?.apiKey;
2292
+ const userId = data.userId ?? socket.handshake?.auth?.userId;
2293
+ const sessionId = data.sessionId ?? socket.handshake?.auth?.sessionId;
2294
+ const session = await agent.connect({ apiKey, userId, sessionId });
2295
+ activeSessions.set(socket.id, session);
2296
+ session.on("audio", (ev) => {
2297
+ socket.emit("vision.audio", {
2298
+ data: ev.data.toString("base64"),
2299
+ mimeType: ev.mimeType ?? "audio/pcm"
2300
+ });
2301
+ });
2302
+ session.on("transcript", (ev) => {
2303
+ socket.emit("vision.transcript", { text: ev.text, role: ev.role });
2304
+ });
2305
+ session.on("text", (ev) => {
2306
+ socket.emit("vision.text", { text: ev.text });
2307
+ });
2308
+ session.on("tool_call_start", (ev) => {
2309
+ socket.emit("vision.tool.call", { name: ev.name, args: ev.args });
2310
+ });
2311
+ session.on("tool_result", (ev) => {
2312
+ socket.emit("vision.tool.result", { name: ev.name, result: ev.result });
2313
+ });
2314
+ session.on("usage", (ev) => {
2315
+ socket.emit("vision.usage", ev);
2316
+ });
2317
+ session.on("interrupted", () => {
2318
+ socket.emit("vision.interrupted");
2319
+ });
2320
+ session.on("error", (ev) => {
2321
+ socket.emit("vision.error", { error: ev.error.message });
2322
+ });
2323
+ session.on("disconnected", () => {
2324
+ activeSessions.delete(socket.id);
2325
+ socket.emit("vision.stopped");
2326
+ });
2327
+ socket.emit("vision.started", { userId });
2328
+ } catch (error) {
2329
+ socket.emit("vision.error", { error: error.message });
2330
+ }
2331
+ }
2332
+ );
2333
+ socket.on("vision.audio", (data) => {
2334
+ const session = activeSessions.get(socket.id);
2335
+ if (!session) return;
2336
+ if (typeof data?.data !== "string" || data.data.length > 1e6) return;
2337
+ try {
2338
+ session.sendAudio(Buffer.from(data.data, "base64"));
2339
+ } catch {
2340
+ socket.emit("vision.error", { error: "Invalid audio data" });
2341
+ }
2342
+ });
2343
+ socket.on("vision.image", (data) => {
2344
+ const session = activeSessions.get(socket.id);
2345
+ if (!session) return;
2346
+ if (typeof data?.data !== "string" || data.data.length > 5e6) return;
2347
+ try {
2348
+ session.sendImage(Buffer.from(data.data, "base64"), data.mimeType ?? "image/jpeg");
2349
+ } catch {
2350
+ socket.emit("vision.error", { error: "Invalid image data" });
2351
+ }
2352
+ });
2353
+ socket.on("vision.text", (data) => {
2354
+ const session = activeSessions.get(socket.id);
2355
+ if (!session) return;
2356
+ if (typeof data?.text !== "string" || data.text.length > 1e4) return;
2357
+ session.sendText(data.text);
2358
+ });
2359
+ socket.on("vision.interrupt", () => {
2360
+ const session = activeSessions.get(socket.id);
2361
+ if (!session) return;
2362
+ session.interrupt();
2363
+ });
2364
+ socket.on("vision.stop", async () => {
2365
+ const session = activeSessions.get(socket.id);
2366
+ if (!session) return;
2367
+ try {
2368
+ await session.close();
2369
+ } catch (err) {
2370
+ console.warn("[vision-gateway] Error closing session:", err);
2371
+ }
2372
+ activeSessions.delete(socket.id);
2373
+ socket.emit("vision.stopped");
2374
+ });
2375
+ socket.on("disconnect", async () => {
2376
+ const session = activeSessions.get(socket.id);
2377
+ if (session) {
2378
+ try {
2379
+ await session.close();
2380
+ } catch (err) {
2381
+ console.warn(
2382
+ "[radaros/vision-gateway] Error closing session on disconnect:",
2383
+ err instanceof Error ? err.message : err
2384
+ );
2385
+ }
2386
+ activeSessions.delete(socket.id);
2387
+ }
2388
+ });
2389
+ });
2390
+ }
2391
+
2040
2392
  // src/socketio/voice-gateway.ts
2041
2393
  function createVoiceGateway(opts) {
2042
2394
  const ns = opts.io.of(opts.namespace ?? "/radaros-voice");
@@ -2177,6 +2529,10 @@ export {
2177
2529
  createAgentRouter,
2178
2530
  createBrowserGateway,
2179
2531
  createFileUploadMiddleware,
2532
+ createGatewayRouter,
2533
+ createJwtMiddleware,
2534
+ createRbacMiddleware,
2535
+ createVisionGateway,
2180
2536
  createVoiceGateway,
2181
2537
  errorHandler,
2182
2538
  generateAgentCard,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@radaros/transport",
3
- "version": "0.3.45",
3
+ "version": "0.3.48",
4
4
  "description": "HTTP and WebSocket transport layer for RadarOS agents",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -42,7 +42,7 @@
42
42
  "typescript": "^5.6.0"
43
43
  },
44
44
  "peerDependencies": {
45
- "@radaros/core": "^0.3.45",
45
+ "@radaros/core": "^0.3.48",
46
46
  "@types/express": "^4.0.0 || ^5.0.0",
47
47
  "express": "^4.0.0 || ^5.0.0",
48
48
  "multer": ">=2.0.0",