@radaros/transport 0.3.2 → 0.3.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@radaros/transport",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -20,7 +20,7 @@
20
20
  "typescript": "^5.6.0"
21
21
  },
22
22
  "peerDependencies": {
23
- "@radaros/core": "^0.3.2",
23
+ "@radaros/core": "^0.3.3",
24
24
  "@types/express": "^4.0.0 || ^5.0.0",
25
25
  "express": "^4.0.0 || ^5.0.0",
26
26
  "multer": ">=1.4.0",
package/dist/index.d.ts DELETED
@@ -1,116 +0,0 @@
1
- import { Agent, Team, Workflow, A2AAgentCard } from '@radaros/core';
2
-
3
- interface FileUploadOptions {
4
- maxFileSize?: number;
5
- maxFiles?: number;
6
- allowedMimeTypes?: string[];
7
- }
8
- declare function createFileUploadMiddleware(opts?: FileUploadOptions): any;
9
- declare function buildMultiModalInput(body: any, files?: any[]): string | any[];
10
-
11
- interface SwaggerOptions {
12
- /** Enable Swagger UI at /docs. Default: false */
13
- enabled?: boolean;
14
- /** API title shown in Swagger UI */
15
- title?: string;
16
- /** API description shown in Swagger UI */
17
- description?: string;
18
- /** API version string */
19
- version?: string;
20
- /** Route prefix used in path generation (e.g. "/api") */
21
- routePrefix?: string;
22
- /** Server URLs for the spec */
23
- servers?: Array<{
24
- url: string;
25
- description?: string;
26
- }>;
27
- /** Path to serve Swagger UI. Default: "/docs" */
28
- docsPath?: string;
29
- /** Path to serve the raw OpenAPI JSON spec. Default: "/docs/spec.json" */
30
- specPath?: string;
31
- }
32
- interface RouterOptions {
33
- agents?: Record<string, Agent>;
34
- teams?: Record<string, Team>;
35
- workflows?: Record<string, Workflow<any>>;
36
- middleware?: any[];
37
- /** Swagger / OpenAPI configuration */
38
- swagger?: SwaggerOptions;
39
- /** File upload configuration for multi-modal inputs */
40
- fileUpload?: boolean | FileUploadOptions;
41
- }
42
-
43
- declare function createAgentRouter(opts: RouterOptions): any;
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;
47
-
48
- interface OpenAPISpec {
49
- openapi: string;
50
- info: {
51
- title: string;
52
- description: string;
53
- version: string;
54
- };
55
- servers?: Array<{
56
- url: string;
57
- description?: string;
58
- }>;
59
- paths: Record<string, Record<string, unknown>>;
60
- components: {
61
- schemas: Record<string, unknown>;
62
- securitySchemes?: Record<string, unknown>;
63
- };
64
- security?: Array<Record<string, string[]>>;
65
- tags: Array<{
66
- name: string;
67
- description: string;
68
- }>;
69
- }
70
- declare function generateOpenAPISpec(routerOpts: RouterOptions, swaggerOpts?: SwaggerOptions): OpenAPISpec;
71
-
72
- interface GatewayOptions {
73
- agents?: Record<string, Agent>;
74
- teams?: Record<string, Team>;
75
- io: any;
76
- namespace?: string;
77
- authMiddleware?: (socket: any, next: (err?: Error) => void) => void;
78
- }
79
-
80
- declare function createAgentGateway(opts: GatewayOptions): void;
81
-
82
- interface A2AServerOptions {
83
- agents: Record<string, Agent>;
84
- basePath?: string;
85
- provider?: {
86
- organization: string;
87
- url?: string;
88
- };
89
- version?: string;
90
- }
91
-
92
- /**
93
- * Mount an A2A-compliant server on an Express app.
94
- *
95
- * - Serves `/.well-known/agent.json` with the Agent Card
96
- * - Handles JSON-RPC 2.0 requests at the basePath for message/send, message/stream, tasks/get, tasks/cancel
97
- */
98
- declare function createA2AServer(app: any, opts: A2AServerOptions): void;
99
-
100
- /**
101
- * Generate an A2A Agent Card from a RadarOS Agent.
102
- * The card is served at /.well-known/agent.json per the A2A spec.
103
- */
104
- declare function generateAgentCard(agent: Agent, serverUrl: string, provider?: {
105
- organization: string;
106
- url?: string;
107
- }, version?: string): A2AAgentCard;
108
- /**
109
- * Generate a combined Agent Card that lists multiple agents as skills.
110
- */
111
- declare function generateMultiAgentCard(agents: Record<string, Agent>, serverUrl: string, provider?: {
112
- organization: string;
113
- url?: string;
114
- }, version?: string): A2AAgentCard;
115
-
116
- export { type A2AServerOptions, type FileUploadOptions, type GatewayOptions, type RouterOptions, type SwaggerOptions, buildMultiModalInput, createA2AServer, createAgentGateway, createAgentRouter, createFileUploadMiddleware, errorHandler, generateAgentCard, generateMultiAgentCard, generateOpenAPISpec, requestLogger };
package/dist/index.js DELETED
@@ -1,1127 +0,0 @@
1
- // src/express/router-factory.ts
2
- import { createRequire as createRequire3 } from "module";
3
-
4
- // src/express/swagger.ts
5
- import { createRequire } from "module";
6
- var _require = createRequire(import.meta.url);
7
- function zodSchemaToJsonSchema(schema) {
8
- try {
9
- const zodToJsonSchema = _require("zod-to-json-schema").default ?? _require("zod-to-json-schema");
10
- const result = zodToJsonSchema(schema, { target: "openApi3" });
11
- const { $schema, ...rest } = result;
12
- return rest;
13
- } catch {
14
- return null;
15
- }
16
- }
17
- var SCHEMAS = {
18
- RunRequest: {
19
- type: "object",
20
- required: ["input"],
21
- properties: {
22
- input: {
23
- oneOf: [
24
- { type: "string", description: "Text input" },
25
- {
26
- type: "array",
27
- description: "Multi-modal content parts",
28
- items: {
29
- oneOf: [
30
- {
31
- type: "object",
32
- properties: {
33
- type: { type: "string", enum: ["text"] },
34
- text: { type: "string" }
35
- },
36
- required: ["type", "text"]
37
- },
38
- {
39
- type: "object",
40
- properties: {
41
- type: { type: "string", enum: ["image"] },
42
- data: { type: "string", description: "Base64 data or URL" },
43
- mimeType: { type: "string", enum: ["image/png", "image/jpeg", "image/gif", "image/webp"] }
44
- },
45
- required: ["type", "data"]
46
- },
47
- {
48
- type: "object",
49
- properties: {
50
- type: { type: "string", enum: ["audio"] },
51
- data: { type: "string", description: "Base64 data or URL" },
52
- mimeType: { type: "string" }
53
- },
54
- required: ["type", "data"]
55
- },
56
- {
57
- type: "object",
58
- properties: {
59
- type: { type: "string", enum: ["file"] },
60
- data: { type: "string", description: "Base64 data or URL" },
61
- mimeType: { type: "string" },
62
- fileName: { type: "string" }
63
- },
64
- required: ["type", "data"]
65
- }
66
- ]
67
- }
68
- }
69
- ]
70
- },
71
- sessionId: { type: "string", description: "Session ID for conversation continuity" },
72
- userId: { type: "string", description: "User identifier" }
73
- }
74
- },
75
- MultipartRunRequest: {
76
- type: "object",
77
- properties: {
78
- input: { type: "string", description: "Text input" },
79
- sessionId: { type: "string" },
80
- userId: { type: "string" },
81
- files: {
82
- type: "array",
83
- items: { type: "string", format: "binary" },
84
- description: "Files to include as multi-modal input (images, audio, documents)"
85
- }
86
- },
87
- required: ["input"]
88
- },
89
- RunOutput: {
90
- type: "object",
91
- properties: {
92
- text: { type: "string", description: "Agent response text" },
93
- toolCalls: {
94
- type: "array",
95
- items: {
96
- type: "object",
97
- properties: {
98
- toolCallId: { type: "string" },
99
- toolName: { type: "string" },
100
- result: {}
101
- }
102
- }
103
- },
104
- usage: {
105
- type: "object",
106
- properties: {
107
- promptTokens: { type: "number" },
108
- completionTokens: { type: "number" },
109
- totalTokens: { type: "number" }
110
- }
111
- },
112
- structured: { description: "Parsed structured output (if schema is configured)" },
113
- durationMs: { type: "number" }
114
- }
115
- },
116
- StreamChunk: {
117
- type: "object",
118
- description: "Server-Sent Event data",
119
- properties: {
120
- type: { type: "string", enum: ["text", "tool_call_start", "tool_call_delta", "tool_call_end", "finish"] },
121
- text: { type: "string" }
122
- }
123
- },
124
- Error: {
125
- type: "object",
126
- properties: {
127
- error: { type: "string" }
128
- }
129
- },
130
- WorkflowRunRequest: {
131
- type: "object",
132
- properties: {
133
- sessionId: { type: "string" },
134
- userId: { type: "string" }
135
- }
136
- }
137
- };
138
- function buildAgentDescription(agent) {
139
- const parts = [];
140
- parts.push(`**Model:** \`${agent.providerId}/${agent.modelId}\``);
141
- if (typeof agent.instructions === "string") {
142
- const instr = agent.instructions.length > 200 ? agent.instructions.slice(0, 200) + "\u2026" : agent.instructions;
143
- parts.push(`**Instructions:** ${instr}`);
144
- }
145
- if (agent.tools?.length > 0) {
146
- const toolNames = agent.tools.map((t) => `\`${t.name}\``).join(", ");
147
- parts.push(`**Tools:** ${toolNames}`);
148
- }
149
- if (agent.hasStructuredOutput) {
150
- parts.push("**Structured Output:** Enabled");
151
- }
152
- return parts.join("\n\n");
153
- }
154
- function generateOpenAPISpec(routerOpts, swaggerOpts = {}) {
155
- const prefix = swaggerOpts.routePrefix ?? "";
156
- const providers = /* @__PURE__ */ new Set();
157
- if (routerOpts.agents) {
158
- for (const agent of Object.values(routerOpts.agents)) {
159
- providers.add(agent.providerId ?? "unknown");
160
- }
161
- }
162
- const securitySchemes = {};
163
- const securityRequirements = [];
164
- if (providers.has("openai")) {
165
- securitySchemes.OpenAIKey = {
166
- type: "apiKey",
167
- in: "header",
168
- name: "x-openai-api-key",
169
- description: "OpenAI API key (sk-...)"
170
- };
171
- securityRequirements.push({ OpenAIKey: [] });
172
- }
173
- if (providers.has("google")) {
174
- securitySchemes.GoogleKey = {
175
- type: "apiKey",
176
- in: "header",
177
- name: "x-google-api-key",
178
- description: "Google AI API key (AIza...)"
179
- };
180
- securityRequirements.push({ GoogleKey: [] });
181
- }
182
- if (providers.has("anthropic")) {
183
- securitySchemes.AnthropicKey = {
184
- type: "apiKey",
185
- in: "header",
186
- name: "x-anthropic-api-key",
187
- description: "Anthropic API key (sk-ant-...)"
188
- };
189
- securityRequirements.push({ AnthropicKey: [] });
190
- }
191
- securitySchemes.GenericKey = {
192
- type: "apiKey",
193
- in: "header",
194
- name: "x-api-key",
195
- description: "Generic API key (used if provider-specific key is not set)"
196
- };
197
- const spec = {
198
- openapi: "3.0.3",
199
- info: {
200
- title: swaggerOpts.title ?? "RadarOS API",
201
- description: swaggerOpts.description ?? "Auto-generated API documentation for RadarOS agents, teams, and workflows.",
202
- version: swaggerOpts.version ?? "1.0.0"
203
- },
204
- paths: {},
205
- components: {
206
- schemas: SCHEMAS,
207
- securitySchemes
208
- },
209
- security: securityRequirements,
210
- tags: []
211
- };
212
- if (swaggerOpts.servers) {
213
- spec.servers = swaggerOpts.servers;
214
- }
215
- if (routerOpts.agents && Object.keys(routerOpts.agents).length > 0) {
216
- spec.tags.push({ name: "Agents", description: "Agent endpoints for running and streaming AI agents" });
217
- for (const [name, agent] of Object.entries(routerOpts.agents)) {
218
- const agentDesc = buildAgentDescription(agent);
219
- let responseSchemaRef = "#/components/schemas/RunOutput";
220
- const zodSchema = agent.structuredOutputSchema;
221
- if (zodSchema) {
222
- const structuredJsonSchema = zodSchemaToJsonSchema(zodSchema);
223
- if (structuredJsonSchema) {
224
- const schemaName = `RunOutput_${name}`;
225
- spec.components.schemas[schemaName] = {
226
- type: "object",
227
- properties: {
228
- text: { type: "string", description: "Raw agent response text" },
229
- toolCalls: {
230
- type: "array",
231
- items: {
232
- type: "object",
233
- properties: {
234
- toolCallId: { type: "string" },
235
- toolName: { type: "string" },
236
- result: {}
237
- }
238
- }
239
- },
240
- usage: {
241
- type: "object",
242
- properties: {
243
- promptTokens: { type: "number" },
244
- completionTokens: { type: "number" },
245
- totalTokens: { type: "number" }
246
- }
247
- },
248
- structured: {
249
- ...structuredJsonSchema,
250
- description: "Parsed structured output"
251
- },
252
- durationMs: { type: "number" }
253
- }
254
- };
255
- responseSchemaRef = `#/components/schemas/${schemaName}`;
256
- }
257
- }
258
- spec.paths[`${prefix}/agents/${name}/run`] = {
259
- post: {
260
- tags: ["Agents"],
261
- summary: `Run agent: ${name}`,
262
- description: agentDesc,
263
- operationId: `runAgent_${name}`,
264
- requestBody: {
265
- required: true,
266
- content: {
267
- "application/json": {
268
- schema: { $ref: "#/components/schemas/RunRequest" }
269
- },
270
- "multipart/form-data": {
271
- schema: { $ref: "#/components/schemas/MultipartRunRequest" }
272
- }
273
- }
274
- },
275
- responses: {
276
- "200": {
277
- description: "Agent run result",
278
- content: {
279
- "application/json": {
280
- schema: { $ref: responseSchemaRef }
281
- }
282
- }
283
- },
284
- "400": {
285
- description: "Bad request",
286
- content: {
287
- "application/json": {
288
- schema: { $ref: "#/components/schemas/Error" }
289
- }
290
- }
291
- },
292
- "500": {
293
- description: "Internal server error",
294
- content: {
295
- "application/json": {
296
- schema: { $ref: "#/components/schemas/Error" }
297
- }
298
- }
299
- }
300
- }
301
- }
302
- };
303
- spec.paths[`${prefix}/agents/${name}/stream`] = {
304
- post: {
305
- tags: ["Agents"],
306
- summary: `Stream agent: ${name}`,
307
- description: `Stream responses from agent **${name}** via Server-Sent Events.
308
-
309
- ${agentDesc}`,
310
- operationId: `streamAgent_${name}`,
311
- requestBody: {
312
- required: true,
313
- content: {
314
- "application/json": {
315
- schema: { $ref: "#/components/schemas/RunRequest" }
316
- }
317
- }
318
- },
319
- responses: {
320
- "200": {
321
- description: "SSE stream of agent chunks",
322
- content: {
323
- "text/event-stream": {
324
- schema: { $ref: "#/components/schemas/StreamChunk" }
325
- }
326
- }
327
- },
328
- "400": {
329
- description: "Bad request",
330
- content: {
331
- "application/json": {
332
- schema: { $ref: "#/components/schemas/Error" }
333
- }
334
- }
335
- }
336
- }
337
- }
338
- };
339
- }
340
- }
341
- if (routerOpts.teams && Object.keys(routerOpts.teams).length > 0) {
342
- spec.tags.push({ name: "Teams", description: "Team endpoints for multi-agent coordination" });
343
- for (const name of Object.keys(routerOpts.teams)) {
344
- spec.paths[`${prefix}/teams/${name}/run`] = {
345
- post: {
346
- tags: ["Teams"],
347
- summary: `Run team: ${name}`,
348
- operationId: `runTeam_${name}`,
349
- requestBody: {
350
- required: true,
351
- content: {
352
- "application/json": {
353
- schema: { $ref: "#/components/schemas/RunRequest" }
354
- }
355
- }
356
- },
357
- responses: {
358
- "200": {
359
- description: "Team run result",
360
- content: {
361
- "application/json": {
362
- schema: { $ref: "#/components/schemas/RunOutput" }
363
- }
364
- }
365
- },
366
- "400": { description: "Bad request", content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } },
367
- "500": { description: "Internal server error", content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } }
368
- }
369
- }
370
- };
371
- spec.paths[`${prefix}/teams/${name}/stream`] = {
372
- post: {
373
- tags: ["Teams"],
374
- summary: `Stream team: ${name}`,
375
- operationId: `streamTeam_${name}`,
376
- requestBody: {
377
- required: true,
378
- content: { "application/json": { schema: { $ref: "#/components/schemas/RunRequest" } } }
379
- },
380
- responses: {
381
- "200": { description: "SSE stream", content: { "text/event-stream": { schema: { $ref: "#/components/schemas/StreamChunk" } } } },
382
- "400": { description: "Bad request", content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } }
383
- }
384
- }
385
- };
386
- }
387
- }
388
- if (routerOpts.workflows && Object.keys(routerOpts.workflows).length > 0) {
389
- spec.tags.push({ name: "Workflows", description: "Workflow endpoints for step-based pipelines" });
390
- for (const name of Object.keys(routerOpts.workflows)) {
391
- spec.paths[`${prefix}/workflows/${name}/run`] = {
392
- post: {
393
- tags: ["Workflows"],
394
- summary: `Run workflow: ${name}`,
395
- operationId: `runWorkflow_${name}`,
396
- requestBody: {
397
- required: true,
398
- content: {
399
- "application/json": {
400
- schema: { $ref: "#/components/schemas/WorkflowRunRequest" }
401
- }
402
- }
403
- },
404
- responses: {
405
- "200": { description: "Workflow result", content: { "application/json": { schema: { type: "object" } } } },
406
- "500": { description: "Internal server error", content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } }
407
- }
408
- }
409
- };
410
- }
411
- }
412
- return spec;
413
- }
414
- function serveSwaggerUI(spec) {
415
- let swaggerUiExpress;
416
- try {
417
- swaggerUiExpress = _require("swagger-ui-express");
418
- } catch {
419
- throw new Error(
420
- "swagger-ui-express is required for Swagger UI. Install it: npm install swagger-ui-express"
421
- );
422
- }
423
- return {
424
- setup: swaggerUiExpress.setup(spec, {
425
- customCss: ".swagger-ui .topbar { display: none }",
426
- customSiteTitle: spec.info.title
427
- }),
428
- serve: swaggerUiExpress.serve
429
- };
430
- }
431
-
432
- // src/express/file-upload.ts
433
- import { createRequire as createRequire2 } from "module";
434
- var _require2 = createRequire2(import.meta.url);
435
- var MIME_TO_PART_TYPE = {
436
- "image/png": "image",
437
- "image/jpeg": "image",
438
- "image/jpg": "image",
439
- "image/gif": "image",
440
- "image/webp": "image",
441
- "audio/mpeg": "audio",
442
- "audio/mp3": "audio",
443
- "audio/wav": "audio",
444
- "audio/ogg": "audio",
445
- "audio/webm": "audio",
446
- "audio/flac": "audio",
447
- "audio/aac": "audio",
448
- "audio/mp4": "audio"
449
- };
450
- function getPartType(mimeType) {
451
- return MIME_TO_PART_TYPE[mimeType] ?? "file";
452
- }
453
- function createFileUploadMiddleware(opts = {}) {
454
- let multer;
455
- try {
456
- multer = _require2("multer");
457
- } catch {
458
- throw new Error(
459
- "multer is required for file uploads. Install it: npm install multer"
460
- );
461
- }
462
- const storage = multer.memoryStorage();
463
- const upload = multer({
464
- storage,
465
- limits: {
466
- fileSize: opts.maxFileSize ?? 50 * 1024 * 1024,
467
- files: opts.maxFiles ?? 10
468
- },
469
- fileFilter: opts.allowedMimeTypes ? (_req, file, cb) => {
470
- if (opts.allowedMimeTypes.includes(file.mimetype)) {
471
- cb(null, true);
472
- } else {
473
- cb(new Error(`File type ${file.mimetype} is not allowed`));
474
- }
475
- } : void 0
476
- });
477
- return upload.array("files", opts.maxFiles ?? 10);
478
- }
479
- function filesToContentParts(files) {
480
- return files.map((file) => {
481
- const base64 = file.buffer.toString("base64");
482
- const partType = getPartType(file.mimetype);
483
- return {
484
- type: partType,
485
- data: base64,
486
- mimeType: file.mimetype,
487
- ...partType === "file" ? { fileName: file.originalname } : {}
488
- };
489
- });
490
- }
491
- function buildMultiModalInput(body, files) {
492
- const textInput = body?.input;
493
- if (!files || files.length === 0) {
494
- return textInput;
495
- }
496
- const parts = [];
497
- if (textInput) {
498
- parts.push({ type: "text", text: textInput });
499
- }
500
- parts.push(...filesToContentParts(files));
501
- return parts;
502
- }
503
-
504
- // src/express/router-factory.ts
505
- var _require3 = createRequire3(import.meta.url);
506
- var API_KEY_HEADERS = {
507
- "x-openai-api-key": "openai",
508
- "x-google-api-key": "google",
509
- "x-anthropic-api-key": "anthropic",
510
- "x-api-key": "_generic"
511
- };
512
- function extractApiKey(req, agent) {
513
- for (const [header, provider] of Object.entries(API_KEY_HEADERS)) {
514
- const value = req.headers[header];
515
- if (value && (provider === "_generic" || provider === agent.providerId)) {
516
- return value;
517
- }
518
- }
519
- return req.body?.apiKey ?? void 0;
520
- }
521
- function createAgentRouter(opts) {
522
- let express;
523
- try {
524
- express = _require3("express");
525
- } catch {
526
- throw new Error(
527
- "express is required for createAgentRouter. Install it: npm install express"
528
- );
529
- }
530
- const router = express.Router();
531
- if (opts.middleware) {
532
- for (const mw of opts.middleware) {
533
- router.use(mw);
534
- }
535
- }
536
- let uploadMiddleware = null;
537
- if (opts.fileUpload) {
538
- const uploadOpts = typeof opts.fileUpload === "object" ? opts.fileUpload : {};
539
- uploadMiddleware = createFileUploadMiddleware(uploadOpts);
540
- }
541
- function withUpload(handler) {
542
- if (!uploadMiddleware) return handler;
543
- return (req, res, next) => {
544
- uploadMiddleware(req, res, (err) => {
545
- if (err) {
546
- return res.status(400).json({ error: err.message });
547
- }
548
- handler(req, res).catch(next);
549
- });
550
- };
551
- }
552
- if (opts.swagger?.enabled) {
553
- const spec = generateOpenAPISpec(opts, opts.swagger);
554
- const docsPath = opts.swagger.docsPath ?? "/docs";
555
- const specPath = opts.swagger.specPath ?? "/docs/spec.json";
556
- router.get(specPath, (_req, res) => {
557
- res.json(spec);
558
- });
559
- try {
560
- const { serve, setup } = serveSwaggerUI(spec);
561
- router.use(docsPath, serve, setup);
562
- } catch (e) {
563
- console.warn(`[radaros:transport] Swagger UI disabled: ${e.message}`);
564
- }
565
- }
566
- if (opts.agents) {
567
- for (const [name, agent] of Object.entries(opts.agents)) {
568
- router.post(
569
- `/agents/${name}/run`,
570
- withUpload(async (req, res) => {
571
- try {
572
- const input = buildMultiModalInput(req.body, req.files);
573
- if (!input) {
574
- return res.status(400).json({ error: "input is required" });
575
- }
576
- const { sessionId, userId } = req.body ?? {};
577
- const apiKey = extractApiKey(req, agent);
578
- const result = await agent.run(input, { sessionId, userId, apiKey });
579
- res.json(result);
580
- } catch (error) {
581
- res.status(500).json({ error: error.message });
582
- }
583
- })
584
- );
585
- router.post(`/agents/${name}/stream`, async (req, res) => {
586
- try {
587
- const { input, sessionId, userId } = req.body ?? {};
588
- if (!input) {
589
- return res.status(400).json({ error: "input is required" });
590
- }
591
- const apiKey = extractApiKey(req, agent);
592
- res.writeHead(200, {
593
- "Content-Type": "text/event-stream",
594
- "Cache-Control": "no-cache",
595
- Connection: "keep-alive"
596
- });
597
- const stream = agent.stream(input, { sessionId, userId, apiKey });
598
- for await (const chunk of stream) {
599
- res.write(`data: ${JSON.stringify(chunk)}
600
-
601
- `);
602
- }
603
- res.write("data: [DONE]\n\n");
604
- res.end();
605
- } catch (error) {
606
- if (!res.headersSent) {
607
- res.status(500).json({ error: error.message });
608
- } else {
609
- res.write(
610
- `data: ${JSON.stringify({ type: "error", error: error.message })}
611
-
612
- `
613
- );
614
- res.end();
615
- }
616
- }
617
- });
618
- }
619
- }
620
- if (opts.teams) {
621
- for (const [name, team] of Object.entries(opts.teams)) {
622
- router.post(`/teams/${name}/run`, async (req, res) => {
623
- try {
624
- const { input, sessionId, userId } = req.body ?? {};
625
- if (!input) {
626
- return res.status(400).json({ error: "input is required" });
627
- }
628
- const apiKey = req.headers["x-api-key"] ?? req.body?.apiKey;
629
- const result = await team.run(input, { sessionId, userId, apiKey });
630
- res.json(result);
631
- } catch (error) {
632
- res.status(500).json({ error: error.message });
633
- }
634
- });
635
- router.post(`/teams/${name}/stream`, async (req, res) => {
636
- try {
637
- const { input, sessionId, userId } = req.body ?? {};
638
- if (!input) {
639
- return res.status(400).json({ error: "input is required" });
640
- }
641
- const apiKey = req.headers["x-api-key"] ?? req.body?.apiKey;
642
- res.writeHead(200, {
643
- "Content-Type": "text/event-stream",
644
- "Cache-Control": "no-cache",
645
- Connection: "keep-alive"
646
- });
647
- const stream = team.stream(input, { sessionId, userId, apiKey });
648
- for await (const chunk of stream) {
649
- res.write(`data: ${JSON.stringify(chunk)}
650
-
651
- `);
652
- }
653
- res.write("data: [DONE]\n\n");
654
- res.end();
655
- } catch (error) {
656
- if (!res.headersSent) {
657
- res.status(500).json({ error: error.message });
658
- } else {
659
- res.write(
660
- `data: ${JSON.stringify({ type: "error", error: error.message })}
661
-
662
- `
663
- );
664
- res.end();
665
- }
666
- }
667
- });
668
- }
669
- }
670
- if (opts.workflows) {
671
- for (const [name, workflow] of Object.entries(opts.workflows)) {
672
- router.post(`/workflows/${name}/run`, async (req, res) => {
673
- try {
674
- const { sessionId, userId } = req.body ?? {};
675
- const result = await workflow.run({ sessionId, userId });
676
- res.json(result);
677
- } catch (error) {
678
- res.status(500).json({ error: error.message });
679
- }
680
- });
681
- }
682
- }
683
- return router;
684
- }
685
-
686
- // src/express/middleware.ts
687
- function errorHandler() {
688
- return (err, _req, res, _next) => {
689
- console.error("[radaros:transport] Error:", err.message);
690
- res.status(err.statusCode ?? 500).json({
691
- error: err.message ?? "Internal server error"
692
- });
693
- };
694
- }
695
- function requestLogger() {
696
- return (req, _res, next) => {
697
- console.log(`[radaros:transport] ${req.method} ${req.path}`);
698
- next();
699
- };
700
- }
701
-
702
- // src/socketio/gateway.ts
703
- function createAgentGateway(opts) {
704
- const ns = opts.io.of(opts.namespace ?? "/radaros");
705
- if (opts.authMiddleware) {
706
- ns.use(opts.authMiddleware);
707
- }
708
- ns.on("connection", (socket) => {
709
- socket.on(
710
- "agent.run",
711
- async (data) => {
712
- const agent = opts.agents?.[data.name];
713
- if (!agent) {
714
- socket.emit("agent.error", {
715
- error: `Agent "${data.name}" not found`
716
- });
717
- return;
718
- }
719
- try {
720
- const apiKey = data.apiKey ?? socket.handshake?.auth?.apiKey;
721
- const sessionId = data.sessionId ?? socket.id;
722
- let fullText = "";
723
- for await (const chunk of agent.stream(data.input, {
724
- sessionId,
725
- apiKey
726
- })) {
727
- if (chunk.type === "text") {
728
- fullText += chunk.text;
729
- socket.emit("agent.chunk", { chunk: chunk.text });
730
- } else if (chunk.type === "tool_call_start") {
731
- socket.emit("agent.tool.call", {
732
- toolName: chunk.toolCall.name,
733
- args: null
734
- });
735
- } else if (chunk.type === "tool_call_end") {
736
- socket.emit("agent.tool.done", { toolCallId: chunk.toolCallId });
737
- }
738
- }
739
- socket.emit("agent.done", { output: { text: fullText } });
740
- } catch (error) {
741
- socket.emit("agent.error", { error: error.message });
742
- }
743
- }
744
- );
745
- socket.on(
746
- "team.run",
747
- async (data) => {
748
- const team = opts.teams?.[data.name];
749
- if (!team) {
750
- socket.emit("agent.error", {
751
- error: `Team "${data.name}" not found`
752
- });
753
- return;
754
- }
755
- try {
756
- const apiKey = data.apiKey ?? socket.handshake?.auth?.apiKey;
757
- const result = await team.run(data.input, {
758
- sessionId: data.sessionId ?? socket.id,
759
- apiKey
760
- });
761
- socket.emit("agent.done", { output: result });
762
- } catch (error) {
763
- socket.emit("agent.error", { error: error.message });
764
- }
765
- }
766
- );
767
- });
768
- }
769
-
770
- // src/a2a/a2a-server.ts
771
- import { createRequire as createRequire4 } from "module";
772
- import { randomUUID } from "crypto";
773
-
774
- // src/a2a/agent-card.ts
775
- function generateAgentCard(agent, serverUrl, provider, version) {
776
- const skills = agent.tools.map((tool) => ({
777
- id: tool.name,
778
- name: tool.name,
779
- description: tool.description
780
- }));
781
- if (skills.length === 0) {
782
- skills.push({
783
- id: "general",
784
- name: "General",
785
- description: typeof agent.instructions === "string" ? agent.instructions.slice(0, 200) : "General-purpose agent"
786
- });
787
- }
788
- const description = typeof agent.instructions === "string" ? agent.instructions : `RadarOS agent: ${agent.name}`;
789
- return {
790
- name: agent.name,
791
- description,
792
- url: serverUrl,
793
- version: version ?? "1.0.0",
794
- provider,
795
- capabilities: {
796
- streaming: true,
797
- pushNotifications: false,
798
- stateTransitionHistory: true
799
- },
800
- skills,
801
- defaultInputModes: ["text/plain"],
802
- defaultOutputModes: ["text/plain"],
803
- supportedInputModes: ["text/plain", "application/json"],
804
- supportedOutputModes: ["text/plain", "application/json"]
805
- };
806
- }
807
- function generateMultiAgentCard(agents, serverUrl, provider, version) {
808
- const skills = Object.entries(agents).map(
809
- ([name, agent]) => ({
810
- id: name,
811
- name: agent.name,
812
- description: typeof agent.instructions === "string" ? agent.instructions.slice(0, 200) : `Agent: ${name}`
813
- })
814
- );
815
- return {
816
- name: "RadarOS Agent Server",
817
- description: `Multi-agent server with ${Object.keys(agents).length} agents: ${Object.keys(agents).join(", ")}`,
818
- url: serverUrl,
819
- version: version ?? "1.0.0",
820
- provider,
821
- capabilities: {
822
- streaming: true,
823
- pushNotifications: false,
824
- stateTransitionHistory: true
825
- },
826
- skills,
827
- defaultInputModes: ["text/plain"],
828
- defaultOutputModes: ["text/plain"],
829
- supportedInputModes: ["text/plain", "application/json"],
830
- supportedOutputModes: ["text/plain", "application/json"]
831
- };
832
- }
833
-
834
- // src/a2a/a2a-server.ts
835
- var _require4 = createRequire4(import.meta.url);
836
- var TaskStore = class {
837
- tasks = /* @__PURE__ */ new Map();
838
- get(id) {
839
- return this.tasks.get(id);
840
- }
841
- set(task) {
842
- this.tasks.set(task.id, task);
843
- }
844
- updateState(id, state, message) {
845
- const task = this.tasks.get(id);
846
- if (!task) return;
847
- task.status = {
848
- state,
849
- message,
850
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
851
- };
852
- }
853
- };
854
- function a2aPartsToText(parts) {
855
- return parts.filter((p) => p.kind === "text").map((p) => p.text).join("\n");
856
- }
857
- function textToA2AParts(text) {
858
- return [{ kind: "text", text }];
859
- }
860
- function jsonRpcError(id, code, message) {
861
- return { jsonrpc: "2.0", id, error: { code, message } };
862
- }
863
- function resolveAgent(agents, message) {
864
- const meta = message.metadata;
865
- const agentName = meta?.agentName;
866
- if (agentName && agents[agentName]) {
867
- return agents[agentName];
868
- }
869
- const names = Object.keys(agents);
870
- if (names.length === 1) {
871
- return agents[names[0]];
872
- }
873
- const textContent = a2aPartsToText(message.parts).toLowerCase();
874
- for (const [name, agent] of Object.entries(agents)) {
875
- if (textContent.includes(name.toLowerCase())) {
876
- return agent;
877
- }
878
- }
879
- return agents[names[0]] ?? null;
880
- }
881
- function createA2AServer(app, opts) {
882
- const express = _require4("express");
883
- const basePath = opts.basePath ?? "/";
884
- const taskStore = new TaskStore();
885
- const serverUrl = basePath === "/" ? "" : basePath;
886
- const agentCard = generateMultiAgentCard(
887
- opts.agents,
888
- serverUrl || "/",
889
- opts.provider,
890
- opts.version
891
- );
892
- app.get("/.well-known/agent.json", (_req, res) => {
893
- res.json(agentCard);
894
- });
895
- app.use(basePath, express.json());
896
- app.post(basePath, async (req, res) => {
897
- const body = req.body;
898
- if (!body || body.jsonrpc !== "2.0" || !body.method) {
899
- return res.status(400).json(
900
- jsonRpcError(body?.id ?? 0, -32600, "Invalid JSON-RPC request")
901
- );
902
- }
903
- try {
904
- switch (body.method) {
905
- case "message/send":
906
- return await handleMessageSend(req, res, body, opts.agents, taskStore);
907
- case "message/stream":
908
- return await handleMessageStream(req, res, body, opts.agents, taskStore);
909
- case "tasks/get":
910
- return handleTasksGet(res, body, taskStore);
911
- case "tasks/cancel":
912
- return handleTasksCancel(res, body, taskStore);
913
- default:
914
- return res.json(
915
- jsonRpcError(body.id, -32601, `Method '${body.method}' not found`)
916
- );
917
- }
918
- } catch (err) {
919
- return res.json(
920
- jsonRpcError(body.id, -32e3, err.message ?? "Internal error")
921
- );
922
- }
923
- });
924
- }
925
- async function handleMessageSend(_req, res, body, agents, store) {
926
- const params = body.params;
927
- const message = params?.message;
928
- if (!message?.parts?.length) {
929
- return res.json(jsonRpcError(body.id, -32602, "Missing message.parts"));
930
- }
931
- const agent = resolveAgent(agents, message);
932
- if (!agent) {
933
- return res.json(jsonRpcError(body.id, -32602, "No matching agent found"));
934
- }
935
- const taskId = randomUUID();
936
- const input = a2aPartsToText(message.parts);
937
- const task = {
938
- id: taskId,
939
- sessionId: params?.sessionId ?? void 0,
940
- status: { state: "submitted", timestamp: (/* @__PURE__ */ new Date()).toISOString() },
941
- history: [message],
942
- metadata: { agentName: agent.name }
943
- };
944
- store.set(task);
945
- store.updateState(taskId, "working");
946
- try {
947
- const result = await agent.run(input, {
948
- sessionId: task.sessionId
949
- });
950
- const responseParts = textToA2AParts(result.text);
951
- if (result.structured) {
952
- responseParts.push({
953
- kind: "data",
954
- data: result.structured
955
- });
956
- }
957
- const agentMessage = {
958
- role: "agent",
959
- parts: responseParts,
960
- messageId: randomUUID(),
961
- taskId
962
- };
963
- task.history.push(agentMessage);
964
- if (result.toolCalls?.length) {
965
- task.artifacts = result.toolCalls.map((tc) => ({
966
- artifactId: tc.toolCallId,
967
- name: tc.toolName,
968
- parts: [
969
- {
970
- kind: "text",
971
- text: typeof tc.result === "string" ? tc.result : tc.result.content
972
- }
973
- ]
974
- }));
975
- }
976
- store.updateState(taskId, "completed", agentMessage);
977
- const response = {
978
- jsonrpc: "2.0",
979
- id: body.id,
980
- result: store.get(taskId)
981
- };
982
- res.json(response);
983
- } catch (err) {
984
- const errorMessage = {
985
- role: "agent",
986
- parts: [{ kind: "text", text: `Error: ${err.message}` }],
987
- taskId
988
- };
989
- store.updateState(taskId, "failed", errorMessage);
990
- res.json(jsonRpcError(body.id, -32e3, err.message));
991
- }
992
- }
993
- async function handleMessageStream(_req, res, body, agents, store) {
994
- const params = body.params;
995
- const message = params?.message;
996
- if (!message?.parts?.length) {
997
- return res.json(jsonRpcError(body.id, -32602, "Missing message.parts"));
998
- }
999
- const agent = resolveAgent(agents, message);
1000
- if (!agent) {
1001
- return res.json(jsonRpcError(body.id, -32602, "No matching agent found"));
1002
- }
1003
- const taskId = randomUUID();
1004
- const input = a2aPartsToText(message.parts);
1005
- const task = {
1006
- id: taskId,
1007
- sessionId: params?.sessionId ?? void 0,
1008
- status: { state: "submitted", timestamp: (/* @__PURE__ */ new Date()).toISOString() },
1009
- history: [message],
1010
- metadata: { agentName: agent.name }
1011
- };
1012
- store.set(task);
1013
- res.writeHead(200, {
1014
- "Content-Type": "text/event-stream",
1015
- "Cache-Control": "no-cache",
1016
- Connection: "keep-alive"
1017
- });
1018
- const sendEvent = (data) => {
1019
- res.write(`data: ${JSON.stringify(data)}
1020
-
1021
- `);
1022
- };
1023
- store.updateState(taskId, "working");
1024
- sendEvent({
1025
- jsonrpc: "2.0",
1026
- id: body.id,
1027
- result: store.get(taskId)
1028
- });
1029
- try {
1030
- let fullText = "";
1031
- for await (const chunk of agent.stream(input, {
1032
- sessionId: task.sessionId
1033
- })) {
1034
- if (chunk.type === "text") {
1035
- fullText += chunk.text;
1036
- sendEvent({
1037
- jsonrpc: "2.0",
1038
- id: body.id,
1039
- result: {
1040
- id: taskId,
1041
- status: {
1042
- state: "working",
1043
- message: {
1044
- role: "agent",
1045
- parts: [{ kind: "text", text: chunk.text }]
1046
- },
1047
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
1048
- }
1049
- }
1050
- });
1051
- }
1052
- }
1053
- const agentMessage = {
1054
- role: "agent",
1055
- parts: textToA2AParts(fullText),
1056
- messageId: randomUUID(),
1057
- taskId
1058
- };
1059
- task.history.push(agentMessage);
1060
- store.updateState(taskId, "completed", agentMessage);
1061
- sendEvent({
1062
- jsonrpc: "2.0",
1063
- id: body.id,
1064
- result: store.get(taskId)
1065
- });
1066
- res.end();
1067
- } catch (err) {
1068
- const errorMessage = {
1069
- role: "agent",
1070
- parts: [{ kind: "text", text: `Error: ${err.message}` }],
1071
- taskId
1072
- };
1073
- store.updateState(taskId, "failed", errorMessage);
1074
- sendEvent({
1075
- jsonrpc: "2.0",
1076
- id: body.id,
1077
- result: store.get(taskId)
1078
- });
1079
- res.end();
1080
- }
1081
- }
1082
- function handleTasksGet(res, body, store) {
1083
- const params = body.params;
1084
- const taskId = params?.id;
1085
- if (!taskId) {
1086
- return res.json(jsonRpcError(body.id, -32602, "Missing task id"));
1087
- }
1088
- const task = store.get(taskId);
1089
- if (!task) {
1090
- return res.json(jsonRpcError(body.id, -32602, `Task '${taskId}' not found`));
1091
- }
1092
- const historyLength = params?.historyLength;
1093
- const result = { ...task };
1094
- if (historyLength && result.history) {
1095
- result.history = result.history.slice(-historyLength);
1096
- }
1097
- res.json({ jsonrpc: "2.0", id: body.id, result });
1098
- }
1099
- function handleTasksCancel(res, body, store) {
1100
- const params = body.params;
1101
- const taskId = params?.id;
1102
- if (!taskId) {
1103
- return res.json(jsonRpcError(body.id, -32602, "Missing task id"));
1104
- }
1105
- const task = store.get(taskId);
1106
- if (!task) {
1107
- return res.json(jsonRpcError(body.id, -32602, `Task '${taskId}' not found`));
1108
- }
1109
- store.updateState(taskId, "canceled");
1110
- res.json({
1111
- jsonrpc: "2.0",
1112
- id: body.id,
1113
- result: store.get(taskId)
1114
- });
1115
- }
1116
- export {
1117
- buildMultiModalInput,
1118
- createA2AServer,
1119
- createAgentGateway,
1120
- createAgentRouter,
1121
- createFileUploadMiddleware,
1122
- errorHandler,
1123
- generateAgentCard,
1124
- generateMultiAgentCard,
1125
- generateOpenAPISpec,
1126
- requestLogger
1127
- };