@radaros/transport 0.1.0

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.js ADDED
@@ -0,0 +1,728 @@
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
+ var SCHEMAS = {
8
+ RunRequest: {
9
+ type: "object",
10
+ required: ["input"],
11
+ properties: {
12
+ input: {
13
+ oneOf: [
14
+ { type: "string", description: "Text input" },
15
+ {
16
+ type: "array",
17
+ description: "Multi-modal content parts",
18
+ items: {
19
+ oneOf: [
20
+ {
21
+ type: "object",
22
+ properties: {
23
+ type: { type: "string", enum: ["text"] },
24
+ text: { type: "string" }
25
+ },
26
+ required: ["type", "text"]
27
+ },
28
+ {
29
+ type: "object",
30
+ properties: {
31
+ type: { type: "string", enum: ["image"] },
32
+ data: { type: "string", description: "Base64 data or URL" },
33
+ mimeType: { type: "string", enum: ["image/png", "image/jpeg", "image/gif", "image/webp"] }
34
+ },
35
+ required: ["type", "data"]
36
+ },
37
+ {
38
+ type: "object",
39
+ properties: {
40
+ type: { type: "string", enum: ["audio"] },
41
+ data: { type: "string", description: "Base64 data or URL" },
42
+ mimeType: { type: "string" }
43
+ },
44
+ required: ["type", "data"]
45
+ },
46
+ {
47
+ type: "object",
48
+ properties: {
49
+ type: { type: "string", enum: ["file"] },
50
+ data: { type: "string", description: "Base64 data or URL" },
51
+ mimeType: { type: "string" },
52
+ fileName: { type: "string" }
53
+ },
54
+ required: ["type", "data"]
55
+ }
56
+ ]
57
+ }
58
+ }
59
+ ]
60
+ },
61
+ sessionId: { type: "string", description: "Session ID for conversation continuity" },
62
+ userId: { type: "string", description: "User identifier" }
63
+ }
64
+ },
65
+ MultipartRunRequest: {
66
+ type: "object",
67
+ properties: {
68
+ input: { type: "string", description: "Text input" },
69
+ sessionId: { type: "string" },
70
+ userId: { type: "string" },
71
+ files: {
72
+ type: "array",
73
+ items: { type: "string", format: "binary" },
74
+ description: "Files to include as multi-modal input (images, audio, documents)"
75
+ }
76
+ },
77
+ required: ["input"]
78
+ },
79
+ RunOutput: {
80
+ type: "object",
81
+ properties: {
82
+ text: { type: "string", description: "Agent response text" },
83
+ toolCalls: {
84
+ type: "array",
85
+ items: {
86
+ type: "object",
87
+ properties: {
88
+ toolCallId: { type: "string" },
89
+ toolName: { type: "string" },
90
+ result: {}
91
+ }
92
+ }
93
+ },
94
+ usage: {
95
+ type: "object",
96
+ properties: {
97
+ promptTokens: { type: "number" },
98
+ completionTokens: { type: "number" },
99
+ totalTokens: { type: "number" }
100
+ }
101
+ },
102
+ structured: { description: "Parsed structured output (if schema is configured)" },
103
+ durationMs: { type: "number" }
104
+ }
105
+ },
106
+ StreamChunk: {
107
+ type: "object",
108
+ description: "Server-Sent Event data",
109
+ properties: {
110
+ type: { type: "string", enum: ["text", "tool_call_start", "tool_call_delta", "tool_call_end", "finish"] },
111
+ text: { type: "string" }
112
+ }
113
+ },
114
+ Error: {
115
+ type: "object",
116
+ properties: {
117
+ error: { type: "string" }
118
+ }
119
+ },
120
+ WorkflowRunRequest: {
121
+ type: "object",
122
+ properties: {
123
+ sessionId: { type: "string" },
124
+ userId: { type: "string" }
125
+ }
126
+ }
127
+ };
128
+ function buildAgentDescription(agent) {
129
+ const parts = [];
130
+ parts.push(`**Model:** \`${agent.providerId}/${agent.modelId}\``);
131
+ if (typeof agent.instructions === "string") {
132
+ const instr = agent.instructions.length > 200 ? agent.instructions.slice(0, 200) + "\u2026" : agent.instructions;
133
+ parts.push(`**Instructions:** ${instr}`);
134
+ }
135
+ if (agent.tools?.length > 0) {
136
+ const toolNames = agent.tools.map((t) => `\`${t.name}\``).join(", ");
137
+ parts.push(`**Tools:** ${toolNames}`);
138
+ }
139
+ if (agent.hasStructuredOutput) {
140
+ parts.push("**Structured Output:** Enabled");
141
+ }
142
+ return parts.join("\n\n");
143
+ }
144
+ function generateOpenAPISpec(routerOpts, swaggerOpts = {}) {
145
+ const prefix = swaggerOpts.routePrefix ?? "";
146
+ const providers = /* @__PURE__ */ new Set();
147
+ if (routerOpts.agents) {
148
+ for (const agent of Object.values(routerOpts.agents)) {
149
+ providers.add(agent.providerId ?? "unknown");
150
+ }
151
+ }
152
+ const securitySchemes = {};
153
+ const securityRequirements = [];
154
+ if (providers.has("openai")) {
155
+ securitySchemes.OpenAIKey = {
156
+ type: "apiKey",
157
+ in: "header",
158
+ name: "x-openai-api-key",
159
+ description: "OpenAI API key (sk-...)"
160
+ };
161
+ securityRequirements.push({ OpenAIKey: [] });
162
+ }
163
+ if (providers.has("google")) {
164
+ securitySchemes.GoogleKey = {
165
+ type: "apiKey",
166
+ in: "header",
167
+ name: "x-google-api-key",
168
+ description: "Google AI API key (AIza...)"
169
+ };
170
+ securityRequirements.push({ GoogleKey: [] });
171
+ }
172
+ if (providers.has("anthropic")) {
173
+ securitySchemes.AnthropicKey = {
174
+ type: "apiKey",
175
+ in: "header",
176
+ name: "x-anthropic-api-key",
177
+ description: "Anthropic API key (sk-ant-...)"
178
+ };
179
+ securityRequirements.push({ AnthropicKey: [] });
180
+ }
181
+ securitySchemes.GenericKey = {
182
+ type: "apiKey",
183
+ in: "header",
184
+ name: "x-api-key",
185
+ description: "Generic API key (used if provider-specific key is not set)"
186
+ };
187
+ const spec = {
188
+ openapi: "3.0.3",
189
+ info: {
190
+ title: swaggerOpts.title ?? "RadarOS API",
191
+ description: swaggerOpts.description ?? "Auto-generated API documentation for RadarOS agents, teams, and workflows.",
192
+ version: swaggerOpts.version ?? "1.0.0"
193
+ },
194
+ paths: {},
195
+ components: {
196
+ schemas: SCHEMAS,
197
+ securitySchemes
198
+ },
199
+ security: securityRequirements,
200
+ tags: []
201
+ };
202
+ if (swaggerOpts.servers) {
203
+ spec.servers = swaggerOpts.servers;
204
+ }
205
+ if (routerOpts.agents && Object.keys(routerOpts.agents).length > 0) {
206
+ spec.tags.push({ name: "Agents", description: "Agent endpoints for running and streaming AI agents" });
207
+ for (const [name, agent] of Object.entries(routerOpts.agents)) {
208
+ const agentDesc = buildAgentDescription(agent);
209
+ spec.paths[`${prefix}/agents/${name}/run`] = {
210
+ post: {
211
+ tags: ["Agents"],
212
+ summary: `Run agent: ${name}`,
213
+ description: agentDesc,
214
+ operationId: `runAgent_${name}`,
215
+ requestBody: {
216
+ required: true,
217
+ content: {
218
+ "application/json": {
219
+ schema: { $ref: "#/components/schemas/RunRequest" }
220
+ },
221
+ "multipart/form-data": {
222
+ schema: { $ref: "#/components/schemas/MultipartRunRequest" }
223
+ }
224
+ }
225
+ },
226
+ responses: {
227
+ "200": {
228
+ description: "Agent run result",
229
+ content: {
230
+ "application/json": {
231
+ schema: { $ref: "#/components/schemas/RunOutput" }
232
+ }
233
+ }
234
+ },
235
+ "400": {
236
+ description: "Bad request",
237
+ content: {
238
+ "application/json": {
239
+ schema: { $ref: "#/components/schemas/Error" }
240
+ }
241
+ }
242
+ },
243
+ "500": {
244
+ description: "Internal server error",
245
+ content: {
246
+ "application/json": {
247
+ schema: { $ref: "#/components/schemas/Error" }
248
+ }
249
+ }
250
+ }
251
+ }
252
+ }
253
+ };
254
+ spec.paths[`${prefix}/agents/${name}/stream`] = {
255
+ post: {
256
+ tags: ["Agents"],
257
+ summary: `Stream agent: ${name}`,
258
+ description: `Stream responses from agent **${name}** via Server-Sent Events.
259
+
260
+ ${agentDesc}`,
261
+ operationId: `streamAgent_${name}`,
262
+ requestBody: {
263
+ required: true,
264
+ content: {
265
+ "application/json": {
266
+ schema: { $ref: "#/components/schemas/RunRequest" }
267
+ }
268
+ }
269
+ },
270
+ responses: {
271
+ "200": {
272
+ description: "SSE stream of agent chunks",
273
+ content: {
274
+ "text/event-stream": {
275
+ schema: { $ref: "#/components/schemas/StreamChunk" }
276
+ }
277
+ }
278
+ },
279
+ "400": {
280
+ description: "Bad request",
281
+ content: {
282
+ "application/json": {
283
+ schema: { $ref: "#/components/schemas/Error" }
284
+ }
285
+ }
286
+ }
287
+ }
288
+ }
289
+ };
290
+ }
291
+ }
292
+ if (routerOpts.teams && Object.keys(routerOpts.teams).length > 0) {
293
+ spec.tags.push({ name: "Teams", description: "Team endpoints for multi-agent coordination" });
294
+ for (const name of Object.keys(routerOpts.teams)) {
295
+ spec.paths[`${prefix}/teams/${name}/run`] = {
296
+ post: {
297
+ tags: ["Teams"],
298
+ summary: `Run team: ${name}`,
299
+ operationId: `runTeam_${name}`,
300
+ requestBody: {
301
+ required: true,
302
+ content: {
303
+ "application/json": {
304
+ schema: { $ref: "#/components/schemas/RunRequest" }
305
+ }
306
+ }
307
+ },
308
+ responses: {
309
+ "200": {
310
+ description: "Team run result",
311
+ content: {
312
+ "application/json": {
313
+ schema: { $ref: "#/components/schemas/RunOutput" }
314
+ }
315
+ }
316
+ },
317
+ "400": { description: "Bad request", content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } },
318
+ "500": { description: "Internal server error", content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } }
319
+ }
320
+ }
321
+ };
322
+ spec.paths[`${prefix}/teams/${name}/stream`] = {
323
+ post: {
324
+ tags: ["Teams"],
325
+ summary: `Stream team: ${name}`,
326
+ operationId: `streamTeam_${name}`,
327
+ requestBody: {
328
+ required: true,
329
+ content: { "application/json": { schema: { $ref: "#/components/schemas/RunRequest" } } }
330
+ },
331
+ responses: {
332
+ "200": { description: "SSE stream", content: { "text/event-stream": { schema: { $ref: "#/components/schemas/StreamChunk" } } } },
333
+ "400": { description: "Bad request", content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } }
334
+ }
335
+ }
336
+ };
337
+ }
338
+ }
339
+ if (routerOpts.workflows && Object.keys(routerOpts.workflows).length > 0) {
340
+ spec.tags.push({ name: "Workflows", description: "Workflow endpoints for step-based pipelines" });
341
+ for (const name of Object.keys(routerOpts.workflows)) {
342
+ spec.paths[`${prefix}/workflows/${name}/run`] = {
343
+ post: {
344
+ tags: ["Workflows"],
345
+ summary: `Run workflow: ${name}`,
346
+ operationId: `runWorkflow_${name}`,
347
+ requestBody: {
348
+ required: true,
349
+ content: {
350
+ "application/json": {
351
+ schema: { $ref: "#/components/schemas/WorkflowRunRequest" }
352
+ }
353
+ }
354
+ },
355
+ responses: {
356
+ "200": { description: "Workflow result", content: { "application/json": { schema: { type: "object" } } } },
357
+ "500": { description: "Internal server error", content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } }
358
+ }
359
+ }
360
+ };
361
+ }
362
+ }
363
+ return spec;
364
+ }
365
+ function serveSwaggerUI(spec) {
366
+ let swaggerUiExpress;
367
+ try {
368
+ swaggerUiExpress = _require("swagger-ui-express");
369
+ } catch {
370
+ throw new Error(
371
+ "swagger-ui-express is required for Swagger UI. Install it: npm install swagger-ui-express"
372
+ );
373
+ }
374
+ return {
375
+ setup: swaggerUiExpress.setup(spec, {
376
+ customCss: ".swagger-ui .topbar { display: none }",
377
+ customSiteTitle: spec.info.title
378
+ }),
379
+ serve: swaggerUiExpress.serve
380
+ };
381
+ }
382
+
383
+ // src/express/file-upload.ts
384
+ import { createRequire as createRequire2 } from "module";
385
+ var _require2 = createRequire2(import.meta.url);
386
+ var MIME_TO_PART_TYPE = {
387
+ "image/png": "image",
388
+ "image/jpeg": "image",
389
+ "image/jpg": "image",
390
+ "image/gif": "image",
391
+ "image/webp": "image",
392
+ "audio/mpeg": "audio",
393
+ "audio/mp3": "audio",
394
+ "audio/wav": "audio",
395
+ "audio/ogg": "audio",
396
+ "audio/webm": "audio",
397
+ "audio/flac": "audio",
398
+ "audio/aac": "audio",
399
+ "audio/mp4": "audio"
400
+ };
401
+ function getPartType(mimeType) {
402
+ return MIME_TO_PART_TYPE[mimeType] ?? "file";
403
+ }
404
+ function createFileUploadMiddleware(opts = {}) {
405
+ let multer;
406
+ try {
407
+ multer = _require2("multer");
408
+ } catch {
409
+ throw new Error(
410
+ "multer is required for file uploads. Install it: npm install multer"
411
+ );
412
+ }
413
+ const storage = multer.memoryStorage();
414
+ const upload = multer({
415
+ storage,
416
+ limits: {
417
+ fileSize: opts.maxFileSize ?? 50 * 1024 * 1024,
418
+ files: opts.maxFiles ?? 10
419
+ },
420
+ fileFilter: opts.allowedMimeTypes ? (_req, file, cb) => {
421
+ if (opts.allowedMimeTypes.includes(file.mimetype)) {
422
+ cb(null, true);
423
+ } else {
424
+ cb(new Error(`File type ${file.mimetype} is not allowed`));
425
+ }
426
+ } : void 0
427
+ });
428
+ return upload.array("files", opts.maxFiles ?? 10);
429
+ }
430
+ function filesToContentParts(files) {
431
+ return files.map((file) => {
432
+ const base64 = file.buffer.toString("base64");
433
+ const partType = getPartType(file.mimetype);
434
+ return {
435
+ type: partType,
436
+ data: base64,
437
+ mimeType: file.mimetype,
438
+ ...partType === "file" ? { fileName: file.originalname } : {}
439
+ };
440
+ });
441
+ }
442
+ function buildMultiModalInput(body, files) {
443
+ const textInput = body?.input;
444
+ if (!files || files.length === 0) {
445
+ return textInput;
446
+ }
447
+ const parts = [];
448
+ if (textInput) {
449
+ parts.push({ type: "text", text: textInput });
450
+ }
451
+ parts.push(...filesToContentParts(files));
452
+ return parts;
453
+ }
454
+
455
+ // src/express/router-factory.ts
456
+ var _require3 = createRequire3(import.meta.url);
457
+ var API_KEY_HEADERS = {
458
+ "x-openai-api-key": "openai",
459
+ "x-google-api-key": "google",
460
+ "x-anthropic-api-key": "anthropic",
461
+ "x-api-key": "_generic"
462
+ };
463
+ function extractApiKey(req, agent) {
464
+ for (const [header, provider] of Object.entries(API_KEY_HEADERS)) {
465
+ const value = req.headers[header];
466
+ if (value && (provider === "_generic" || provider === agent.providerId)) {
467
+ return value;
468
+ }
469
+ }
470
+ return req.body?.apiKey ?? void 0;
471
+ }
472
+ function createAgentRouter(opts) {
473
+ let express;
474
+ try {
475
+ express = _require3("express");
476
+ } catch {
477
+ throw new Error(
478
+ "express is required for createAgentRouter. Install it: npm install express"
479
+ );
480
+ }
481
+ const router = express.Router();
482
+ if (opts.middleware) {
483
+ for (const mw of opts.middleware) {
484
+ router.use(mw);
485
+ }
486
+ }
487
+ let uploadMiddleware = null;
488
+ if (opts.fileUpload) {
489
+ const uploadOpts = typeof opts.fileUpload === "object" ? opts.fileUpload : {};
490
+ uploadMiddleware = createFileUploadMiddleware(uploadOpts);
491
+ }
492
+ function withUpload(handler) {
493
+ if (!uploadMiddleware) return handler;
494
+ return (req, res, next) => {
495
+ uploadMiddleware(req, res, (err) => {
496
+ if (err) {
497
+ return res.status(400).json({ error: err.message });
498
+ }
499
+ handler(req, res).catch(next);
500
+ });
501
+ };
502
+ }
503
+ if (opts.swagger?.enabled) {
504
+ const spec = generateOpenAPISpec(opts, opts.swagger);
505
+ const docsPath = opts.swagger.docsPath ?? "/docs";
506
+ const specPath = opts.swagger.specPath ?? "/docs/spec.json";
507
+ router.get(specPath, (_req, res) => {
508
+ res.json(spec);
509
+ });
510
+ try {
511
+ const { serve, setup } = serveSwaggerUI(spec);
512
+ router.use(docsPath, serve, setup);
513
+ } catch (e) {
514
+ console.warn(`[radaros:transport] Swagger UI disabled: ${e.message}`);
515
+ }
516
+ }
517
+ if (opts.agents) {
518
+ for (const [name, agent] of Object.entries(opts.agents)) {
519
+ router.post(
520
+ `/agents/${name}/run`,
521
+ withUpload(async (req, res) => {
522
+ try {
523
+ const input = buildMultiModalInput(req.body, req.files);
524
+ if (!input) {
525
+ return res.status(400).json({ error: "input is required" });
526
+ }
527
+ const { sessionId, userId } = req.body ?? {};
528
+ const apiKey = extractApiKey(req, agent);
529
+ const result = await agent.run(input, { sessionId, userId, apiKey });
530
+ res.json(result);
531
+ } catch (error) {
532
+ res.status(500).json({ error: error.message });
533
+ }
534
+ })
535
+ );
536
+ router.post(`/agents/${name}/stream`, async (req, res) => {
537
+ try {
538
+ const { input, sessionId, userId } = req.body ?? {};
539
+ if (!input) {
540
+ return res.status(400).json({ error: "input is required" });
541
+ }
542
+ const apiKey = extractApiKey(req, agent);
543
+ res.writeHead(200, {
544
+ "Content-Type": "text/event-stream",
545
+ "Cache-Control": "no-cache",
546
+ Connection: "keep-alive"
547
+ });
548
+ const stream = agent.stream(input, { sessionId, userId, apiKey });
549
+ for await (const chunk of stream) {
550
+ res.write(`data: ${JSON.stringify(chunk)}
551
+
552
+ `);
553
+ }
554
+ res.write("data: [DONE]\n\n");
555
+ res.end();
556
+ } catch (error) {
557
+ if (!res.headersSent) {
558
+ res.status(500).json({ error: error.message });
559
+ } else {
560
+ res.write(
561
+ `data: ${JSON.stringify({ type: "error", error: error.message })}
562
+
563
+ `
564
+ );
565
+ res.end();
566
+ }
567
+ }
568
+ });
569
+ }
570
+ }
571
+ if (opts.teams) {
572
+ for (const [name, team] of Object.entries(opts.teams)) {
573
+ router.post(`/teams/${name}/run`, async (req, res) => {
574
+ try {
575
+ const { input, sessionId, userId } = req.body ?? {};
576
+ if (!input) {
577
+ return res.status(400).json({ error: "input is required" });
578
+ }
579
+ const apiKey = req.headers["x-api-key"] ?? req.body?.apiKey;
580
+ const result = await team.run(input, { sessionId, userId, apiKey });
581
+ res.json(result);
582
+ } catch (error) {
583
+ res.status(500).json({ error: error.message });
584
+ }
585
+ });
586
+ router.post(`/teams/${name}/stream`, async (req, res) => {
587
+ try {
588
+ const { input, sessionId, userId } = req.body ?? {};
589
+ if (!input) {
590
+ return res.status(400).json({ error: "input is required" });
591
+ }
592
+ const apiKey = req.headers["x-api-key"] ?? req.body?.apiKey;
593
+ res.writeHead(200, {
594
+ "Content-Type": "text/event-stream",
595
+ "Cache-Control": "no-cache",
596
+ Connection: "keep-alive"
597
+ });
598
+ const stream = team.stream(input, { sessionId, userId, apiKey });
599
+ for await (const chunk of stream) {
600
+ res.write(`data: ${JSON.stringify(chunk)}
601
+
602
+ `);
603
+ }
604
+ res.write("data: [DONE]\n\n");
605
+ res.end();
606
+ } catch (error) {
607
+ if (!res.headersSent) {
608
+ res.status(500).json({ error: error.message });
609
+ } else {
610
+ res.write(
611
+ `data: ${JSON.stringify({ type: "error", error: error.message })}
612
+
613
+ `
614
+ );
615
+ res.end();
616
+ }
617
+ }
618
+ });
619
+ }
620
+ }
621
+ if (opts.workflows) {
622
+ for (const [name, workflow] of Object.entries(opts.workflows)) {
623
+ router.post(`/workflows/${name}/run`, async (req, res) => {
624
+ try {
625
+ const { sessionId, userId } = req.body ?? {};
626
+ const result = await workflow.run({ sessionId, userId });
627
+ res.json(result);
628
+ } catch (error) {
629
+ res.status(500).json({ error: error.message });
630
+ }
631
+ });
632
+ }
633
+ }
634
+ return router;
635
+ }
636
+
637
+ // src/express/middleware.ts
638
+ function errorHandler() {
639
+ return (err, _req, res, _next) => {
640
+ console.error("[radaros:transport] Error:", err.message);
641
+ res.status(err.statusCode ?? 500).json({
642
+ error: err.message ?? "Internal server error"
643
+ });
644
+ };
645
+ }
646
+ function requestLogger() {
647
+ return (req, _res, next) => {
648
+ console.log(`[radaros:transport] ${req.method} ${req.path}`);
649
+ next();
650
+ };
651
+ }
652
+
653
+ // src/socketio/gateway.ts
654
+ function createAgentGateway(opts) {
655
+ const ns = opts.io.of(opts.namespace ?? "/radaros");
656
+ if (opts.authMiddleware) {
657
+ ns.use(opts.authMiddleware);
658
+ }
659
+ ns.on("connection", (socket) => {
660
+ socket.on(
661
+ "agent.run",
662
+ async (data) => {
663
+ const agent = opts.agents?.[data.name];
664
+ if (!agent) {
665
+ socket.emit("agent.error", {
666
+ error: `Agent "${data.name}" not found`
667
+ });
668
+ return;
669
+ }
670
+ try {
671
+ const apiKey = data.apiKey ?? socket.handshake?.auth?.apiKey;
672
+ const sessionId = data.sessionId ?? socket.id;
673
+ let fullText = "";
674
+ for await (const chunk of agent.stream(data.input, {
675
+ sessionId,
676
+ apiKey
677
+ })) {
678
+ if (chunk.type === "text") {
679
+ fullText += chunk.text;
680
+ socket.emit("agent.chunk", { chunk: chunk.text });
681
+ } else if (chunk.type === "tool_call_start") {
682
+ socket.emit("agent.tool.call", {
683
+ toolName: chunk.toolCall.name,
684
+ args: null
685
+ });
686
+ } else if (chunk.type === "tool_call_end") {
687
+ socket.emit("agent.tool.done", { toolCallId: chunk.toolCallId });
688
+ }
689
+ }
690
+ socket.emit("agent.done", { output: { text: fullText } });
691
+ } catch (error) {
692
+ socket.emit("agent.error", { error: error.message });
693
+ }
694
+ }
695
+ );
696
+ socket.on(
697
+ "team.run",
698
+ async (data) => {
699
+ const team = opts.teams?.[data.name];
700
+ if (!team) {
701
+ socket.emit("agent.error", {
702
+ error: `Team "${data.name}" not found`
703
+ });
704
+ return;
705
+ }
706
+ try {
707
+ const apiKey = data.apiKey ?? socket.handshake?.auth?.apiKey;
708
+ const result = await team.run(data.input, {
709
+ sessionId: data.sessionId ?? socket.id,
710
+ apiKey
711
+ });
712
+ socket.emit("agent.done", { output: result });
713
+ } catch (error) {
714
+ socket.emit("agent.error", { error: error.message });
715
+ }
716
+ }
717
+ );
718
+ });
719
+ }
720
+ export {
721
+ buildMultiModalInput,
722
+ createAgentGateway,
723
+ createAgentRouter,
724
+ createFileUploadMiddleware,
725
+ errorHandler,
726
+ generateOpenAPISpec,
727
+ requestLogger
728
+ };