@radaros/transport 0.3.5 → 0.3.6
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.d.ts +116 -0
- package/dist/index.js +1127 -0
- package/package.json +7 -3
- package/src/a2a/a2a-server.ts +0 -391
- package/src/a2a/agent-card.ts +0 -95
- package/src/a2a/types.ts +0 -11
- package/src/express/file-upload.ts +0 -88
- package/src/express/middleware.ts +0 -15
- package/src/express/router-factory.ts +0 -208
- package/src/express/swagger.ts +0 -476
- package/src/express/types.ts +0 -32
- package/src/index.ts +0 -14
- package/src/socketio/gateway.ts +0 -75
- package/src/socketio/handlers.ts +0 -1
- package/src/socketio/types.ts +0 -9
- package/tsconfig.json +0 -11
|
@@ -1,208 +0,0 @@
|
|
|
1
|
-
import { createRequire } from "node:module";
|
|
2
|
-
import type { RouterOptions } from "./types.js";
|
|
3
|
-
import { generateOpenAPISpec, serveSwaggerUI } from "./swagger.js";
|
|
4
|
-
import { createFileUploadMiddleware, buildMultiModalInput } from "./file-upload.js";
|
|
5
|
-
|
|
6
|
-
const _require = createRequire(import.meta.url);
|
|
7
|
-
|
|
8
|
-
const API_KEY_HEADERS: Record<string, string> = {
|
|
9
|
-
"x-openai-api-key": "openai",
|
|
10
|
-
"x-google-api-key": "google",
|
|
11
|
-
"x-anthropic-api-key": "anthropic",
|
|
12
|
-
"x-api-key": "_generic",
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
function extractApiKey(req: any, agent: any): string | undefined {
|
|
16
|
-
for (const [header, provider] of Object.entries(API_KEY_HEADERS)) {
|
|
17
|
-
const value = req.headers[header];
|
|
18
|
-
if (value && (provider === "_generic" || provider === agent.providerId)) {
|
|
19
|
-
return value;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
return req.body?.apiKey ?? undefined;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function createAgentRouter(opts: RouterOptions) {
|
|
26
|
-
let express: any;
|
|
27
|
-
try {
|
|
28
|
-
express = _require("express");
|
|
29
|
-
} catch {
|
|
30
|
-
throw new Error(
|
|
31
|
-
"express is required for createAgentRouter. Install it: npm install express"
|
|
32
|
-
);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const router = express.Router();
|
|
36
|
-
|
|
37
|
-
if (opts.middleware) {
|
|
38
|
-
for (const mw of opts.middleware) {
|
|
39
|
-
router.use(mw);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// ── File upload middleware (lazy-initialized) ───────────────────────────
|
|
44
|
-
let uploadMiddleware: any = null;
|
|
45
|
-
if (opts.fileUpload) {
|
|
46
|
-
const uploadOpts = typeof opts.fileUpload === "object" ? opts.fileUpload : {};
|
|
47
|
-
uploadMiddleware = createFileUploadMiddleware(uploadOpts);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function withUpload(handler: (req: any, res: any) => Promise<void>) {
|
|
51
|
-
if (!uploadMiddleware) return handler;
|
|
52
|
-
return (req: any, res: any, next: any) => {
|
|
53
|
-
uploadMiddleware(req, res, (err: any) => {
|
|
54
|
-
if (err) {
|
|
55
|
-
return res.status(400).json({ error: err.message });
|
|
56
|
-
}
|
|
57
|
-
handler(req, res).catch(next);
|
|
58
|
-
});
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// ── Swagger UI ──────────────────────────────────────────────────────────
|
|
63
|
-
if (opts.swagger?.enabled) {
|
|
64
|
-
const spec = generateOpenAPISpec(opts, opts.swagger);
|
|
65
|
-
const docsPath = opts.swagger.docsPath ?? "/docs";
|
|
66
|
-
const specPath = opts.swagger.specPath ?? "/docs/spec.json";
|
|
67
|
-
|
|
68
|
-
router.get(specPath, (_req: any, res: any) => {
|
|
69
|
-
res.json(spec);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
try {
|
|
73
|
-
const { serve, setup } = serveSwaggerUI(spec);
|
|
74
|
-
router.use(docsPath, serve, setup);
|
|
75
|
-
} catch (e: any) {
|
|
76
|
-
console.warn(`[radaros:transport] Swagger UI disabled: ${e.message}`);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// ── Agent endpoints ─────────────────────────────────────────────────────
|
|
81
|
-
if (opts.agents) {
|
|
82
|
-
for (const [name, agent] of Object.entries(opts.agents)) {
|
|
83
|
-
router.post(
|
|
84
|
-
`/agents/${name}/run`,
|
|
85
|
-
withUpload(async (req: any, res: any) => {
|
|
86
|
-
try {
|
|
87
|
-
const input = buildMultiModalInput(req.body, req.files);
|
|
88
|
-
if (!input) {
|
|
89
|
-
return res.status(400).json({ error: "input is required" });
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const { sessionId, userId } = req.body ?? {};
|
|
93
|
-
const apiKey = extractApiKey(req, agent);
|
|
94
|
-
const result = await agent.run(input, { sessionId, userId, apiKey });
|
|
95
|
-
res.json(result);
|
|
96
|
-
} catch (error: any) {
|
|
97
|
-
res.status(500).json({ error: error.message });
|
|
98
|
-
}
|
|
99
|
-
})
|
|
100
|
-
);
|
|
101
|
-
|
|
102
|
-
router.post(`/agents/${name}/stream`, async (req: any, res: any) => {
|
|
103
|
-
try {
|
|
104
|
-
const { input, sessionId, userId } = req.body ?? {};
|
|
105
|
-
if (!input) {
|
|
106
|
-
return res.status(400).json({ error: "input is required" });
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const apiKey = extractApiKey(req, agent);
|
|
110
|
-
|
|
111
|
-
res.writeHead(200, {
|
|
112
|
-
"Content-Type": "text/event-stream",
|
|
113
|
-
"Cache-Control": "no-cache",
|
|
114
|
-
Connection: "keep-alive",
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
const stream = agent.stream(input, { sessionId, userId, apiKey });
|
|
118
|
-
for await (const chunk of stream) {
|
|
119
|
-
res.write(`data: ${JSON.stringify(chunk)}\n\n`);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
res.write("data: [DONE]\n\n");
|
|
123
|
-
res.end();
|
|
124
|
-
} catch (error: any) {
|
|
125
|
-
if (!res.headersSent) {
|
|
126
|
-
res.status(500).json({ error: error.message });
|
|
127
|
-
} else {
|
|
128
|
-
res.write(
|
|
129
|
-
`data: ${JSON.stringify({ type: "error", error: error.message })}\n\n`
|
|
130
|
-
);
|
|
131
|
-
res.end();
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// ── Team endpoints ──────────────────────────────────────────────────────
|
|
139
|
-
if (opts.teams) {
|
|
140
|
-
for (const [name, team] of Object.entries(opts.teams)) {
|
|
141
|
-
router.post(`/teams/${name}/run`, async (req: any, res: any) => {
|
|
142
|
-
try {
|
|
143
|
-
const { input, sessionId, userId } = req.body ?? {};
|
|
144
|
-
if (!input) {
|
|
145
|
-
return res.status(400).json({ error: "input is required" });
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
const apiKey = req.headers["x-api-key"] ?? req.body?.apiKey;
|
|
149
|
-
const result = await team.run(input, { sessionId, userId, apiKey });
|
|
150
|
-
res.json(result);
|
|
151
|
-
} catch (error: any) {
|
|
152
|
-
res.status(500).json({ error: error.message });
|
|
153
|
-
}
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
router.post(`/teams/${name}/stream`, async (req: any, res: any) => {
|
|
157
|
-
try {
|
|
158
|
-
const { input, sessionId, userId } = req.body ?? {};
|
|
159
|
-
if (!input) {
|
|
160
|
-
return res.status(400).json({ error: "input is required" });
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
const apiKey = req.headers["x-api-key"] ?? req.body?.apiKey;
|
|
164
|
-
|
|
165
|
-
res.writeHead(200, {
|
|
166
|
-
"Content-Type": "text/event-stream",
|
|
167
|
-
"Cache-Control": "no-cache",
|
|
168
|
-
Connection: "keep-alive",
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
const stream = team.stream(input, { sessionId, userId, apiKey });
|
|
172
|
-
for await (const chunk of stream) {
|
|
173
|
-
res.write(`data: ${JSON.stringify(chunk)}\n\n`);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
res.write("data: [DONE]\n\n");
|
|
177
|
-
res.end();
|
|
178
|
-
} catch (error: any) {
|
|
179
|
-
if (!res.headersSent) {
|
|
180
|
-
res.status(500).json({ error: error.message });
|
|
181
|
-
} else {
|
|
182
|
-
res.write(
|
|
183
|
-
`data: ${JSON.stringify({ type: "error", error: error.message })}\n\n`
|
|
184
|
-
);
|
|
185
|
-
res.end();
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// ── Workflow endpoints ──────────────────────────────────────────────────
|
|
193
|
-
if (opts.workflows) {
|
|
194
|
-
for (const [name, workflow] of Object.entries(opts.workflows)) {
|
|
195
|
-
router.post(`/workflows/${name}/run`, async (req: any, res: any) => {
|
|
196
|
-
try {
|
|
197
|
-
const { sessionId, userId } = req.body ?? {};
|
|
198
|
-
const result = await workflow.run({ sessionId, userId });
|
|
199
|
-
res.json(result);
|
|
200
|
-
} catch (error: any) {
|
|
201
|
-
res.status(500).json({ error: error.message });
|
|
202
|
-
}
|
|
203
|
-
});
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
return router;
|
|
208
|
-
}
|
package/src/express/swagger.ts
DELETED
|
@@ -1,476 +0,0 @@
|
|
|
1
|
-
import { createRequire } from "node:module";
|
|
2
|
-
import type { RouterOptions, SwaggerOptions } from "./types.js";
|
|
3
|
-
|
|
4
|
-
const _require = createRequire(import.meta.url);
|
|
5
|
-
|
|
6
|
-
function zodSchemaToJsonSchema(schema: any): Record<string, unknown> | null {
|
|
7
|
-
try {
|
|
8
|
-
const zodToJsonSchema = _require("zod-to-json-schema").default ?? _require("zod-to-json-schema");
|
|
9
|
-
const result = zodToJsonSchema(schema, { target: "openApi3" });
|
|
10
|
-
const { $schema, ...rest } = result as Record<string, unknown>;
|
|
11
|
-
return rest;
|
|
12
|
-
} catch {
|
|
13
|
-
return null;
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
interface OpenAPISpec {
|
|
18
|
-
openapi: string;
|
|
19
|
-
info: {
|
|
20
|
-
title: string;
|
|
21
|
-
description: string;
|
|
22
|
-
version: string;
|
|
23
|
-
};
|
|
24
|
-
servers?: Array<{ url: string; description?: string }>;
|
|
25
|
-
paths: Record<string, Record<string, unknown>>;
|
|
26
|
-
components: {
|
|
27
|
-
schemas: Record<string, unknown>;
|
|
28
|
-
securitySchemes?: Record<string, unknown>;
|
|
29
|
-
};
|
|
30
|
-
security?: Array<Record<string, string[]>>;
|
|
31
|
-
tags: Array<{ name: string; description: string }>;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const SCHEMAS = {
|
|
35
|
-
RunRequest: {
|
|
36
|
-
type: "object",
|
|
37
|
-
required: ["input"],
|
|
38
|
-
properties: {
|
|
39
|
-
input: {
|
|
40
|
-
oneOf: [
|
|
41
|
-
{ type: "string", description: "Text input" },
|
|
42
|
-
{
|
|
43
|
-
type: "array",
|
|
44
|
-
description: "Multi-modal content parts",
|
|
45
|
-
items: {
|
|
46
|
-
oneOf: [
|
|
47
|
-
{
|
|
48
|
-
type: "object",
|
|
49
|
-
properties: {
|
|
50
|
-
type: { type: "string", enum: ["text"] },
|
|
51
|
-
text: { type: "string" },
|
|
52
|
-
},
|
|
53
|
-
required: ["type", "text"],
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
type: "object",
|
|
57
|
-
properties: {
|
|
58
|
-
type: { type: "string", enum: ["image"] },
|
|
59
|
-
data: { type: "string", description: "Base64 data or URL" },
|
|
60
|
-
mimeType: { type: "string", enum: ["image/png", "image/jpeg", "image/gif", "image/webp"] },
|
|
61
|
-
},
|
|
62
|
-
required: ["type", "data"],
|
|
63
|
-
},
|
|
64
|
-
{
|
|
65
|
-
type: "object",
|
|
66
|
-
properties: {
|
|
67
|
-
type: { type: "string", enum: ["audio"] },
|
|
68
|
-
data: { type: "string", description: "Base64 data or URL" },
|
|
69
|
-
mimeType: { type: "string" },
|
|
70
|
-
},
|
|
71
|
-
required: ["type", "data"],
|
|
72
|
-
},
|
|
73
|
-
{
|
|
74
|
-
type: "object",
|
|
75
|
-
properties: {
|
|
76
|
-
type: { type: "string", enum: ["file"] },
|
|
77
|
-
data: { type: "string", description: "Base64 data or URL" },
|
|
78
|
-
mimeType: { type: "string" },
|
|
79
|
-
fileName: { type: "string" },
|
|
80
|
-
},
|
|
81
|
-
required: ["type", "data"],
|
|
82
|
-
},
|
|
83
|
-
],
|
|
84
|
-
},
|
|
85
|
-
},
|
|
86
|
-
],
|
|
87
|
-
},
|
|
88
|
-
sessionId: { type: "string", description: "Session ID for conversation continuity" },
|
|
89
|
-
userId: { type: "string", description: "User identifier" },
|
|
90
|
-
},
|
|
91
|
-
},
|
|
92
|
-
MultipartRunRequest: {
|
|
93
|
-
type: "object",
|
|
94
|
-
properties: {
|
|
95
|
-
input: { type: "string", description: "Text input" },
|
|
96
|
-
sessionId: { type: "string" },
|
|
97
|
-
userId: { type: "string" },
|
|
98
|
-
files: {
|
|
99
|
-
type: "array",
|
|
100
|
-
items: { type: "string", format: "binary" },
|
|
101
|
-
description: "Files to include as multi-modal input (images, audio, documents)",
|
|
102
|
-
},
|
|
103
|
-
},
|
|
104
|
-
required: ["input"],
|
|
105
|
-
},
|
|
106
|
-
RunOutput: {
|
|
107
|
-
type: "object",
|
|
108
|
-
properties: {
|
|
109
|
-
text: { type: "string", description: "Agent response text" },
|
|
110
|
-
toolCalls: {
|
|
111
|
-
type: "array",
|
|
112
|
-
items: {
|
|
113
|
-
type: "object",
|
|
114
|
-
properties: {
|
|
115
|
-
toolCallId: { type: "string" },
|
|
116
|
-
toolName: { type: "string" },
|
|
117
|
-
result: {},
|
|
118
|
-
},
|
|
119
|
-
},
|
|
120
|
-
},
|
|
121
|
-
usage: {
|
|
122
|
-
type: "object",
|
|
123
|
-
properties: {
|
|
124
|
-
promptTokens: { type: "number" },
|
|
125
|
-
completionTokens: { type: "number" },
|
|
126
|
-
totalTokens: { type: "number" },
|
|
127
|
-
},
|
|
128
|
-
},
|
|
129
|
-
structured: { description: "Parsed structured output (if schema is configured)" },
|
|
130
|
-
durationMs: { type: "number" },
|
|
131
|
-
},
|
|
132
|
-
},
|
|
133
|
-
StreamChunk: {
|
|
134
|
-
type: "object",
|
|
135
|
-
description: "Server-Sent Event data",
|
|
136
|
-
properties: {
|
|
137
|
-
type: { type: "string", enum: ["text", "tool_call_start", "tool_call_delta", "tool_call_end", "finish"] },
|
|
138
|
-
text: { type: "string" },
|
|
139
|
-
},
|
|
140
|
-
},
|
|
141
|
-
Error: {
|
|
142
|
-
type: "object",
|
|
143
|
-
properties: {
|
|
144
|
-
error: { type: "string" },
|
|
145
|
-
},
|
|
146
|
-
},
|
|
147
|
-
WorkflowRunRequest: {
|
|
148
|
-
type: "object",
|
|
149
|
-
properties: {
|
|
150
|
-
sessionId: { type: "string" },
|
|
151
|
-
userId: { type: "string" },
|
|
152
|
-
},
|
|
153
|
-
},
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
function buildAgentDescription(agent: any): string {
|
|
157
|
-
const parts: string[] = [];
|
|
158
|
-
parts.push(`**Model:** \`${agent.providerId}/${agent.modelId}\``);
|
|
159
|
-
|
|
160
|
-
if (typeof agent.instructions === "string") {
|
|
161
|
-
const instr = agent.instructions.length > 200
|
|
162
|
-
? agent.instructions.slice(0, 200) + "…"
|
|
163
|
-
: agent.instructions;
|
|
164
|
-
parts.push(`**Instructions:** ${instr}`);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
if (agent.tools?.length > 0) {
|
|
168
|
-
const toolNames = agent.tools.map((t: any) => `\`${t.name}\``).join(", ");
|
|
169
|
-
parts.push(`**Tools:** ${toolNames}`);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
if (agent.hasStructuredOutput) {
|
|
173
|
-
parts.push("**Structured Output:** Enabled");
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
return parts.join("\n\n");
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
export function generateOpenAPISpec(
|
|
180
|
-
routerOpts: RouterOptions,
|
|
181
|
-
swaggerOpts: SwaggerOptions = {}
|
|
182
|
-
): OpenAPISpec {
|
|
183
|
-
const prefix = swaggerOpts.routePrefix ?? "";
|
|
184
|
-
|
|
185
|
-
const providers = new Set<string>();
|
|
186
|
-
if (routerOpts.agents) {
|
|
187
|
-
for (const agent of Object.values(routerOpts.agents)) {
|
|
188
|
-
providers.add((agent as any).providerId ?? "unknown");
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
const securitySchemes: Record<string, unknown> = {};
|
|
193
|
-
const securityRequirements: Array<Record<string, string[]>> = [];
|
|
194
|
-
|
|
195
|
-
if (providers.has("openai")) {
|
|
196
|
-
securitySchemes.OpenAIKey = {
|
|
197
|
-
type: "apiKey",
|
|
198
|
-
in: "header",
|
|
199
|
-
name: "x-openai-api-key",
|
|
200
|
-
description: "OpenAI API key (sk-...)",
|
|
201
|
-
};
|
|
202
|
-
securityRequirements.push({ OpenAIKey: [] });
|
|
203
|
-
}
|
|
204
|
-
if (providers.has("google")) {
|
|
205
|
-
securitySchemes.GoogleKey = {
|
|
206
|
-
type: "apiKey",
|
|
207
|
-
in: "header",
|
|
208
|
-
name: "x-google-api-key",
|
|
209
|
-
description: "Google AI API key (AIza...)",
|
|
210
|
-
};
|
|
211
|
-
securityRequirements.push({ GoogleKey: [] });
|
|
212
|
-
}
|
|
213
|
-
if (providers.has("anthropic")) {
|
|
214
|
-
securitySchemes.AnthropicKey = {
|
|
215
|
-
type: "apiKey",
|
|
216
|
-
in: "header",
|
|
217
|
-
name: "x-anthropic-api-key",
|
|
218
|
-
description: "Anthropic API key (sk-ant-...)",
|
|
219
|
-
};
|
|
220
|
-
securityRequirements.push({ AnthropicKey: [] });
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
securitySchemes.GenericKey = {
|
|
224
|
-
type: "apiKey",
|
|
225
|
-
in: "header",
|
|
226
|
-
name: "x-api-key",
|
|
227
|
-
description: "Generic API key (used if provider-specific key is not set)",
|
|
228
|
-
};
|
|
229
|
-
|
|
230
|
-
const spec: OpenAPISpec = {
|
|
231
|
-
openapi: "3.0.3",
|
|
232
|
-
info: {
|
|
233
|
-
title: swaggerOpts.title ?? "RadarOS API",
|
|
234
|
-
description: swaggerOpts.description ?? "Auto-generated API documentation for RadarOS agents, teams, and workflows.",
|
|
235
|
-
version: swaggerOpts.version ?? "1.0.0",
|
|
236
|
-
},
|
|
237
|
-
paths: {},
|
|
238
|
-
components: {
|
|
239
|
-
schemas: SCHEMAS as Record<string, unknown>,
|
|
240
|
-
securitySchemes,
|
|
241
|
-
},
|
|
242
|
-
security: securityRequirements,
|
|
243
|
-
tags: [],
|
|
244
|
-
};
|
|
245
|
-
|
|
246
|
-
if (swaggerOpts.servers) {
|
|
247
|
-
spec.servers = swaggerOpts.servers;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
if (routerOpts.agents && Object.keys(routerOpts.agents).length > 0) {
|
|
251
|
-
spec.tags.push({ name: "Agents", description: "Agent endpoints for running and streaming AI agents" });
|
|
252
|
-
|
|
253
|
-
for (const [name, agent] of Object.entries(routerOpts.agents)) {
|
|
254
|
-
const agentDesc = buildAgentDescription(agent);
|
|
255
|
-
|
|
256
|
-
let responseSchemaRef = "#/components/schemas/RunOutput";
|
|
257
|
-
|
|
258
|
-
const zodSchema = (agent as any).structuredOutputSchema;
|
|
259
|
-
if (zodSchema) {
|
|
260
|
-
const structuredJsonSchema = zodSchemaToJsonSchema(zodSchema);
|
|
261
|
-
if (structuredJsonSchema) {
|
|
262
|
-
const schemaName = `RunOutput_${name}`;
|
|
263
|
-
(spec.components.schemas as Record<string, unknown>)[schemaName] = {
|
|
264
|
-
type: "object",
|
|
265
|
-
properties: {
|
|
266
|
-
text: { type: "string", description: "Raw agent response text" },
|
|
267
|
-
toolCalls: {
|
|
268
|
-
type: "array",
|
|
269
|
-
items: {
|
|
270
|
-
type: "object",
|
|
271
|
-
properties: {
|
|
272
|
-
toolCallId: { type: "string" },
|
|
273
|
-
toolName: { type: "string" },
|
|
274
|
-
result: {},
|
|
275
|
-
},
|
|
276
|
-
},
|
|
277
|
-
},
|
|
278
|
-
usage: {
|
|
279
|
-
type: "object",
|
|
280
|
-
properties: {
|
|
281
|
-
promptTokens: { type: "number" },
|
|
282
|
-
completionTokens: { type: "number" },
|
|
283
|
-
totalTokens: { type: "number" },
|
|
284
|
-
},
|
|
285
|
-
},
|
|
286
|
-
structured: {
|
|
287
|
-
...structuredJsonSchema,
|
|
288
|
-
description: "Parsed structured output",
|
|
289
|
-
},
|
|
290
|
-
durationMs: { type: "number" },
|
|
291
|
-
},
|
|
292
|
-
};
|
|
293
|
-
responseSchemaRef = `#/components/schemas/${schemaName}`;
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
spec.paths[`${prefix}/agents/${name}/run`] = {
|
|
298
|
-
post: {
|
|
299
|
-
tags: ["Agents"],
|
|
300
|
-
summary: `Run agent: ${name}`,
|
|
301
|
-
description: agentDesc,
|
|
302
|
-
operationId: `runAgent_${name}`,
|
|
303
|
-
requestBody: {
|
|
304
|
-
required: true,
|
|
305
|
-
content: {
|
|
306
|
-
"application/json": {
|
|
307
|
-
schema: { $ref: "#/components/schemas/RunRequest" },
|
|
308
|
-
},
|
|
309
|
-
"multipart/form-data": {
|
|
310
|
-
schema: { $ref: "#/components/schemas/MultipartRunRequest" },
|
|
311
|
-
},
|
|
312
|
-
},
|
|
313
|
-
},
|
|
314
|
-
responses: {
|
|
315
|
-
"200": {
|
|
316
|
-
description: "Agent run result",
|
|
317
|
-
content: {
|
|
318
|
-
"application/json": {
|
|
319
|
-
schema: { $ref: responseSchemaRef },
|
|
320
|
-
},
|
|
321
|
-
},
|
|
322
|
-
},
|
|
323
|
-
"400": {
|
|
324
|
-
description: "Bad request",
|
|
325
|
-
content: {
|
|
326
|
-
"application/json": {
|
|
327
|
-
schema: { $ref: "#/components/schemas/Error" },
|
|
328
|
-
},
|
|
329
|
-
},
|
|
330
|
-
},
|
|
331
|
-
"500": {
|
|
332
|
-
description: "Internal server error",
|
|
333
|
-
content: {
|
|
334
|
-
"application/json": {
|
|
335
|
-
schema: { $ref: "#/components/schemas/Error" },
|
|
336
|
-
},
|
|
337
|
-
},
|
|
338
|
-
},
|
|
339
|
-
},
|
|
340
|
-
},
|
|
341
|
-
};
|
|
342
|
-
|
|
343
|
-
spec.paths[`${prefix}/agents/${name}/stream`] = {
|
|
344
|
-
post: {
|
|
345
|
-
tags: ["Agents"],
|
|
346
|
-
summary: `Stream agent: ${name}`,
|
|
347
|
-
description: `Stream responses from agent **${name}** via Server-Sent Events.\n\n${agentDesc}`,
|
|
348
|
-
operationId: `streamAgent_${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: "SSE stream of agent chunks",
|
|
360
|
-
content: {
|
|
361
|
-
"text/event-stream": {
|
|
362
|
-
schema: { $ref: "#/components/schemas/StreamChunk" },
|
|
363
|
-
},
|
|
364
|
-
},
|
|
365
|
-
},
|
|
366
|
-
"400": {
|
|
367
|
-
description: "Bad request",
|
|
368
|
-
content: {
|
|
369
|
-
"application/json": {
|
|
370
|
-
schema: { $ref: "#/components/schemas/Error" },
|
|
371
|
-
},
|
|
372
|
-
},
|
|
373
|
-
},
|
|
374
|
-
},
|
|
375
|
-
},
|
|
376
|
-
};
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
if (routerOpts.teams && Object.keys(routerOpts.teams).length > 0) {
|
|
381
|
-
spec.tags.push({ name: "Teams", description: "Team endpoints for multi-agent coordination" });
|
|
382
|
-
|
|
383
|
-
for (const name of Object.keys(routerOpts.teams)) {
|
|
384
|
-
spec.paths[`${prefix}/teams/${name}/run`] = {
|
|
385
|
-
post: {
|
|
386
|
-
tags: ["Teams"],
|
|
387
|
-
summary: `Run team: ${name}`,
|
|
388
|
-
operationId: `runTeam_${name}`,
|
|
389
|
-
requestBody: {
|
|
390
|
-
required: true,
|
|
391
|
-
content: {
|
|
392
|
-
"application/json": {
|
|
393
|
-
schema: { $ref: "#/components/schemas/RunRequest" },
|
|
394
|
-
},
|
|
395
|
-
},
|
|
396
|
-
},
|
|
397
|
-
responses: {
|
|
398
|
-
"200": {
|
|
399
|
-
description: "Team run result",
|
|
400
|
-
content: {
|
|
401
|
-
"application/json": {
|
|
402
|
-
schema: { $ref: "#/components/schemas/RunOutput" },
|
|
403
|
-
},
|
|
404
|
-
},
|
|
405
|
-
},
|
|
406
|
-
"400": { description: "Bad request", content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } },
|
|
407
|
-
"500": { description: "Internal server error", content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } },
|
|
408
|
-
},
|
|
409
|
-
},
|
|
410
|
-
};
|
|
411
|
-
|
|
412
|
-
spec.paths[`${prefix}/teams/${name}/stream`] = {
|
|
413
|
-
post: {
|
|
414
|
-
tags: ["Teams"],
|
|
415
|
-
summary: `Stream team: ${name}`,
|
|
416
|
-
operationId: `streamTeam_${name}`,
|
|
417
|
-
requestBody: {
|
|
418
|
-
required: true,
|
|
419
|
-
content: { "application/json": { schema: { $ref: "#/components/schemas/RunRequest" } } },
|
|
420
|
-
},
|
|
421
|
-
responses: {
|
|
422
|
-
"200": { description: "SSE stream", content: { "text/event-stream": { schema: { $ref: "#/components/schemas/StreamChunk" } } } },
|
|
423
|
-
"400": { description: "Bad request", content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } },
|
|
424
|
-
},
|
|
425
|
-
},
|
|
426
|
-
};
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
if (routerOpts.workflows && Object.keys(routerOpts.workflows).length > 0) {
|
|
431
|
-
spec.tags.push({ name: "Workflows", description: "Workflow endpoints for step-based pipelines" });
|
|
432
|
-
|
|
433
|
-
for (const name of Object.keys(routerOpts.workflows)) {
|
|
434
|
-
spec.paths[`${prefix}/workflows/${name}/run`] = {
|
|
435
|
-
post: {
|
|
436
|
-
tags: ["Workflows"],
|
|
437
|
-
summary: `Run workflow: ${name}`,
|
|
438
|
-
operationId: `runWorkflow_${name}`,
|
|
439
|
-
requestBody: {
|
|
440
|
-
required: true,
|
|
441
|
-
content: {
|
|
442
|
-
"application/json": {
|
|
443
|
-
schema: { $ref: "#/components/schemas/WorkflowRunRequest" },
|
|
444
|
-
},
|
|
445
|
-
},
|
|
446
|
-
},
|
|
447
|
-
responses: {
|
|
448
|
-
"200": { description: "Workflow result", content: { "application/json": { schema: { type: "object" } } } },
|
|
449
|
-
"500": { description: "Internal server error", content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } },
|
|
450
|
-
},
|
|
451
|
-
},
|
|
452
|
-
};
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
return spec;
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
export function serveSwaggerUI(spec: OpenAPISpec) {
|
|
460
|
-
let swaggerUiExpress: any;
|
|
461
|
-
try {
|
|
462
|
-
swaggerUiExpress = _require("swagger-ui-express");
|
|
463
|
-
} catch {
|
|
464
|
-
throw new Error(
|
|
465
|
-
"swagger-ui-express is required for Swagger UI. Install it: npm install swagger-ui-express"
|
|
466
|
-
);
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
return {
|
|
470
|
-
setup: swaggerUiExpress.setup(spec, {
|
|
471
|
-
customCss: ".swagger-ui .topbar { display: none }",
|
|
472
|
-
customSiteTitle: spec.info.title,
|
|
473
|
-
}),
|
|
474
|
-
serve: swaggerUiExpress.serve,
|
|
475
|
-
};
|
|
476
|
-
}
|