@radaros/transport 0.3.46 → 0.3.49

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,9 @@ __export(index_exports, {
28
28
  createAgentRouter: () => createAgentRouter,
29
29
  createBrowserGateway: () => createBrowserGateway,
30
30
  createFileUploadMiddleware: () => createFileUploadMiddleware,
31
+ createGatewayRouter: () => createGatewayRouter,
32
+ createJwtMiddleware: () => createJwtMiddleware,
33
+ createRbacMiddleware: () => createRbacMiddleware,
31
34
  createVisionGateway: () => createVisionGateway,
32
35
  createVoiceGateway: () => createVoiceGateway,
33
36
  errorHandler: () => errorHandler,
@@ -722,6 +725,165 @@ function buildMultiModalInput(body, files) {
722
725
  return parts;
723
726
  }
724
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
+
725
887
  // src/express/middleware.ts
726
888
  function errorHandler(options) {
727
889
  const log = options?.logger ?? console;
@@ -743,16 +905,81 @@ function requestLogger(options) {
743
905
  };
744
906
  }
745
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
+
746
973
  // src/express/router-factory.ts
747
- var import_node_module5 = require("module");
974
+ var import_node_module7 = require("module");
748
975
  var import_core3 = require("@radaros/core");
749
976
 
750
977
  // src/express/swagger.ts
751
- var import_node_module4 = require("module");
752
- var _require4 = (0, import_node_module4.createRequire)(importMetaUrl);
978
+ var import_node_module6 = require("module");
979
+ var _require6 = (0, import_node_module6.createRequire)(importMetaUrl);
753
980
  function zodSchemaToJsonSchema(schema) {
754
981
  try {
755
- 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");
756
983
  const result = zodToJsonSchema(schema, { target: "openApi3" });
757
984
  const { $schema, ...rest } = result;
758
985
  return rest;
@@ -1188,7 +1415,7 @@ ${agentDesc}`,
1188
1415
  function serveSwaggerUI(spec) {
1189
1416
  let swaggerUiExpress;
1190
1417
  try {
1191
- swaggerUiExpress = _require4("swagger-ui-express");
1418
+ swaggerUiExpress = _require6("swagger-ui-express");
1192
1419
  } catch {
1193
1420
  throw new Error("swagger-ui-express is required for Swagger UI. Install it: npm install swagger-ui-express");
1194
1421
  }
@@ -1202,7 +1429,7 @@ function serveSwaggerUI(spec) {
1202
1429
  }
1203
1430
 
1204
1431
  // src/express/router-factory.ts
1205
- var _require5 = (0, import_node_module5.createRequire)(importMetaUrl);
1432
+ var _require7 = (0, import_node_module7.createRequire)(importMetaUrl);
1206
1433
  function corsMiddleware(origins) {
1207
1434
  return (req, res, next) => {
1208
1435
  const origin = req.headers.origin;
@@ -1302,7 +1529,7 @@ function createAgentRouter(opts) {
1302
1529
  const reg = opts.registry === false ? null : opts.registry ?? import_core3.registry;
1303
1530
  let express;
1304
1531
  try {
1305
- express = _require5("express");
1532
+ express = _require7("express");
1306
1533
  } catch {
1307
1534
  throw new Error("express is required for createAgentRouter. Install it: npm install express");
1308
1535
  }
@@ -1314,6 +1541,12 @@ function createAgentRouter(opts) {
1314
1541
  const config = opts.rateLimit === true ? {} : opts.rateLimit;
1315
1542
  router.use(rateLimitMiddleware(config));
1316
1543
  }
1544
+ if (opts.jwt) {
1545
+ router.use(createJwtMiddleware(opts.jwt));
1546
+ }
1547
+ if (opts.rbac) {
1548
+ router.use(createRbacMiddleware(opts.rbac));
1549
+ }
1317
1550
  if (opts.middleware) {
1318
1551
  for (const mw of opts.middleware) {
1319
1552
  router.use(mw);
@@ -2344,6 +2577,9 @@ function createVoiceGateway(opts) {
2344
2577
  createAgentRouter,
2345
2578
  createBrowserGateway,
2346
2579
  createFileUploadMiddleware,
2580
+ createGatewayRouter,
2581
+ createJwtMiddleware,
2582
+ createRbacMiddleware,
2347
2583
  createVisionGateway,
2348
2584
  createVoiceGateway,
2349
2585
  errorHandler,
package/dist/index.d.cts CHANGED
@@ -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;
213
242
  }
243
+ interface GatewayConfig {
244
+ locals?: RouterOptions;
245
+ remotes: RemoteEndpoint[];
246
+ healthCheckIntervalMs?: number;
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
 
@@ -348,4 +390,4 @@ interface VoiceGatewayOptions {
348
390
  }
349
391
  declare function createVoiceGateway(opts: VoiceGatewayOptions): void;
350
392
 
351
- export { type A2AServerOptions, type AdminRouterOptions, type BrowserGatewayOptions, type FileUploadOptions, type GatewayOptions, MCPManager, type MCPServerEntry, type MCPServerSummary, type RouterOptions, type SwaggerOptions, type VisionGatewayOptions, type VoiceGatewayOptions, buildMultiModalInput, createA2AServer, createAdminRouter, createAgentGateway, createAgentRouter, createBrowserGateway, createFileUploadMiddleware, createVisionGateway, 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
@@ -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;
213
242
  }
243
+ interface GatewayConfig {
244
+ locals?: RouterOptions;
245
+ remotes: RemoteEndpoint[];
246
+ healthCheckIntervalMs?: number;
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
 
@@ -348,4 +390,4 @@ interface VoiceGatewayOptions {
348
390
  }
349
391
  declare function createVoiceGateway(opts: VoiceGatewayOptions): void;
350
392
 
351
- export { type A2AServerOptions, type AdminRouterOptions, type BrowserGatewayOptions, type FileUploadOptions, type GatewayOptions, MCPManager, type MCPServerEntry, type MCPServerSummary, type RouterOptions, type SwaggerOptions, type VisionGatewayOptions, type VoiceGatewayOptions, buildMultiModalInput, createA2AServer, createAdminRouter, createAgentGateway, createAgentRouter, createBrowserGateway, createFileUploadMiddleware, createVisionGateway, 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);
@@ -2299,6 +2529,9 @@ export {
2299
2529
  createAgentRouter,
2300
2530
  createBrowserGateway,
2301
2531
  createFileUploadMiddleware,
2532
+ createGatewayRouter,
2533
+ createJwtMiddleware,
2534
+ createRbacMiddleware,
2302
2535
  createVisionGateway,
2303
2536
  createVoiceGateway,
2304
2537
  errorHandler,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@radaros/transport",
3
- "version": "0.3.46",
3
+ "version": "0.3.49",
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.46",
45
+ "@radaros/core": "^0.3.49",
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",