@lobu/gateway 3.0.9 → 3.0.13

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.
Files changed (212) hide show
  1. package/dist/api/platform.d.ts.map +1 -1
  2. package/dist/api/platform.js +7 -26
  3. package/dist/api/platform.js.map +1 -1
  4. package/dist/auth/mcp/proxy.d.ts +14 -0
  5. package/dist/auth/mcp/proxy.d.ts.map +1 -1
  6. package/dist/auth/mcp/proxy.js +149 -13
  7. package/dist/auth/mcp/proxy.js.map +1 -1
  8. package/dist/cli/gateway.d.ts.map +1 -1
  9. package/dist/cli/gateway.js +29 -0
  10. package/dist/cli/gateway.js.map +1 -1
  11. package/dist/connections/chat-instance-manager.d.ts.map +1 -1
  12. package/dist/connections/chat-instance-manager.js +2 -1
  13. package/dist/connections/chat-instance-manager.js.map +1 -1
  14. package/dist/connections/interaction-bridge.d.ts +9 -2
  15. package/dist/connections/interaction-bridge.d.ts.map +1 -1
  16. package/dist/connections/interaction-bridge.js +121 -261
  17. package/dist/connections/interaction-bridge.js.map +1 -1
  18. package/dist/gateway/index.js +1 -1
  19. package/dist/gateway/index.js.map +1 -1
  20. package/dist/interactions.d.ts +9 -43
  21. package/dist/interactions.d.ts.map +1 -1
  22. package/dist/interactions.js +10 -52
  23. package/dist/interactions.js.map +1 -1
  24. package/dist/routes/public/agent.d.ts +4 -0
  25. package/dist/routes/public/agent.d.ts.map +1 -1
  26. package/dist/routes/public/agent.js +21 -0
  27. package/dist/routes/public/agent.js.map +1 -1
  28. package/dist/services/core-services.d.ts.map +1 -1
  29. package/dist/services/core-services.js +4 -0
  30. package/dist/services/core-services.js.map +1 -1
  31. package/package.json +9 -9
  32. package/src/__tests__/agent-config-routes.test.ts +0 -254
  33. package/src/__tests__/agent-history-routes.test.ts +0 -72
  34. package/src/__tests__/agent-routes.test.ts +0 -68
  35. package/src/__tests__/agent-schedules-routes.test.ts +0 -59
  36. package/src/__tests__/agent-settings-store.test.ts +0 -323
  37. package/src/__tests__/bedrock-model-catalog.test.ts +0 -40
  38. package/src/__tests__/bedrock-openai-service.test.ts +0 -157
  39. package/src/__tests__/bedrock-provider-module.test.ts +0 -56
  40. package/src/__tests__/chat-instance-manager-slack.test.ts +0 -204
  41. package/src/__tests__/chat-response-bridge.test.ts +0 -131
  42. package/src/__tests__/config-memory-plugins.test.ts +0 -92
  43. package/src/__tests__/config-request-store.test.ts +0 -127
  44. package/src/__tests__/connection-routes.test.ts +0 -144
  45. package/src/__tests__/core-services-store-selection.test.ts +0 -92
  46. package/src/__tests__/docker-deployment.test.ts +0 -1211
  47. package/src/__tests__/embedded-deployment.test.ts +0 -342
  48. package/src/__tests__/grant-store.test.ts +0 -148
  49. package/src/__tests__/http-proxy.test.ts +0 -281
  50. package/src/__tests__/instruction-service.test.ts +0 -37
  51. package/src/__tests__/link-buttons.test.ts +0 -112
  52. package/src/__tests__/lobu.test.ts +0 -32
  53. package/src/__tests__/mcp-config-service.test.ts +0 -347
  54. package/src/__tests__/mcp-proxy.test.ts +0 -694
  55. package/src/__tests__/message-handler-bridge.test.ts +0 -17
  56. package/src/__tests__/model-selection.test.ts +0 -172
  57. package/src/__tests__/oauth-templates.test.ts +0 -39
  58. package/src/__tests__/platform-adapter-slack-send.test.ts +0 -114
  59. package/src/__tests__/platform-helpers-model-resolution.test.ts +0 -253
  60. package/src/__tests__/provider-inheritance.test.ts +0 -212
  61. package/src/__tests__/routes/cli-auth.test.ts +0 -337
  62. package/src/__tests__/routes/interactions.test.ts +0 -121
  63. package/src/__tests__/secret-proxy.test.ts +0 -85
  64. package/src/__tests__/session-manager.test.ts +0 -572
  65. package/src/__tests__/setup.ts +0 -133
  66. package/src/__tests__/skill-and-mcp-registry.test.ts +0 -203
  67. package/src/__tests__/slack-routes.test.ts +0 -161
  68. package/src/__tests__/system-config-resolver.test.ts +0 -75
  69. package/src/__tests__/system-message-limiter.test.ts +0 -89
  70. package/src/__tests__/system-skills-service.test.ts +0 -362
  71. package/src/__tests__/transcription-service.test.ts +0 -222
  72. package/src/__tests__/utils/rate-limiter.test.ts +0 -102
  73. package/src/__tests__/worker-connection-manager.test.ts +0 -497
  74. package/src/__tests__/worker-job-router.test.ts +0 -722
  75. package/src/api/index.ts +0 -1
  76. package/src/api/platform.ts +0 -292
  77. package/src/api/response-renderer.ts +0 -157
  78. package/src/auth/agent-metadata-store.ts +0 -168
  79. package/src/auth/api-auth-middleware.ts +0 -69
  80. package/src/auth/api-key-provider-module.ts +0 -213
  81. package/src/auth/base-provider-module.ts +0 -201
  82. package/src/auth/bedrock/provider-module.ts +0 -110
  83. package/src/auth/chatgpt/chatgpt-oauth-module.ts +0 -185
  84. package/src/auth/chatgpt/device-code-client.ts +0 -218
  85. package/src/auth/chatgpt/index.ts +0 -1
  86. package/src/auth/claude/oauth-module.ts +0 -280
  87. package/src/auth/cli/token-service.ts +0 -249
  88. package/src/auth/external/client.ts +0 -560
  89. package/src/auth/external/device-code-client.ts +0 -235
  90. package/src/auth/mcp/config-service.ts +0 -420
  91. package/src/auth/mcp/proxy.ts +0 -1086
  92. package/src/auth/mcp/string-substitution.ts +0 -17
  93. package/src/auth/mcp/tool-cache.ts +0 -90
  94. package/src/auth/oauth/base-client.ts +0 -267
  95. package/src/auth/oauth/client.ts +0 -153
  96. package/src/auth/oauth/credentials.ts +0 -7
  97. package/src/auth/oauth/providers.ts +0 -69
  98. package/src/auth/oauth/state-store.ts +0 -150
  99. package/src/auth/oauth-templates.ts +0 -179
  100. package/src/auth/provider-catalog.ts +0 -220
  101. package/src/auth/provider-model-options.ts +0 -41
  102. package/src/auth/settings/agent-settings-store.ts +0 -565
  103. package/src/auth/settings/auth-profiles-manager.ts +0 -216
  104. package/src/auth/settings/index.ts +0 -12
  105. package/src/auth/settings/model-preference-store.ts +0 -52
  106. package/src/auth/settings/model-selection.ts +0 -135
  107. package/src/auth/settings/resolved-settings-view.ts +0 -298
  108. package/src/auth/settings/template-utils.ts +0 -44
  109. package/src/auth/settings/token-service.ts +0 -88
  110. package/src/auth/system-env-store.ts +0 -98
  111. package/src/auth/user-agents-store.ts +0 -68
  112. package/src/channels/binding-service.ts +0 -214
  113. package/src/channels/index.ts +0 -4
  114. package/src/cli/gateway.ts +0 -1312
  115. package/src/cli/index.ts +0 -74
  116. package/src/commands/built-in-commands.ts +0 -80
  117. package/src/commands/command-dispatcher.ts +0 -94
  118. package/src/commands/command-reply-adapters.ts +0 -27
  119. package/src/config/file-loader.ts +0 -618
  120. package/src/config/index.ts +0 -588
  121. package/src/config/network-allowlist.ts +0 -71
  122. package/src/connections/chat-instance-manager.ts +0 -1284
  123. package/src/connections/chat-response-bridge.ts +0 -618
  124. package/src/connections/index.ts +0 -7
  125. package/src/connections/interaction-bridge.ts +0 -831
  126. package/src/connections/message-handler-bridge.ts +0 -440
  127. package/src/connections/platform-auth-methods.ts +0 -15
  128. package/src/connections/types.ts +0 -84
  129. package/src/gateway/connection-manager.ts +0 -291
  130. package/src/gateway/index.ts +0 -698
  131. package/src/gateway/job-router.ts +0 -201
  132. package/src/gateway-main.ts +0 -200
  133. package/src/index.ts +0 -41
  134. package/src/infrastructure/queue/index.ts +0 -12
  135. package/src/infrastructure/queue/queue-producer.ts +0 -148
  136. package/src/infrastructure/queue/redis-queue.ts +0 -361
  137. package/src/infrastructure/queue/types.ts +0 -133
  138. package/src/infrastructure/redis/system-message-limiter.ts +0 -94
  139. package/src/interactions/config-request-store.ts +0 -198
  140. package/src/interactions.ts +0 -363
  141. package/src/lobu.ts +0 -311
  142. package/src/metrics/prometheus.ts +0 -159
  143. package/src/modules/module-system.ts +0 -179
  144. package/src/orchestration/base-deployment-manager.ts +0 -900
  145. package/src/orchestration/deployment-utils.ts +0 -98
  146. package/src/orchestration/impl/docker-deployment.ts +0 -620
  147. package/src/orchestration/impl/embedded-deployment.ts +0 -268
  148. package/src/orchestration/impl/index.ts +0 -8
  149. package/src/orchestration/impl/k8s/deployment.ts +0 -1061
  150. package/src/orchestration/impl/k8s/helpers.ts +0 -610
  151. package/src/orchestration/impl/k8s/index.ts +0 -1
  152. package/src/orchestration/index.ts +0 -333
  153. package/src/orchestration/message-consumer.ts +0 -584
  154. package/src/orchestration/scheduled-wakeup.ts +0 -704
  155. package/src/permissions/approval-policy.ts +0 -36
  156. package/src/permissions/grant-store.ts +0 -219
  157. package/src/platform/file-handler.ts +0 -66
  158. package/src/platform/link-buttons.ts +0 -57
  159. package/src/platform/renderer-utils.ts +0 -44
  160. package/src/platform/response-renderer.ts +0 -84
  161. package/src/platform/unified-thread-consumer.ts +0 -194
  162. package/src/platform.ts +0 -318
  163. package/src/proxy/http-proxy.ts +0 -752
  164. package/src/proxy/proxy-manager.ts +0 -81
  165. package/src/proxy/secret-proxy.ts +0 -402
  166. package/src/proxy/token-refresh-job.ts +0 -143
  167. package/src/routes/internal/audio.ts +0 -141
  168. package/src/routes/internal/device-auth.ts +0 -652
  169. package/src/routes/internal/files.ts +0 -226
  170. package/src/routes/internal/history.ts +0 -69
  171. package/src/routes/internal/images.ts +0 -127
  172. package/src/routes/internal/interactions.ts +0 -84
  173. package/src/routes/internal/middleware.ts +0 -23
  174. package/src/routes/internal/schedule.ts +0 -226
  175. package/src/routes/internal/types.ts +0 -22
  176. package/src/routes/openapi-auto.ts +0 -239
  177. package/src/routes/public/agent-access.ts +0 -23
  178. package/src/routes/public/agent-config.ts +0 -675
  179. package/src/routes/public/agent-history.ts +0 -422
  180. package/src/routes/public/agent-schedules.ts +0 -296
  181. package/src/routes/public/agent.ts +0 -1086
  182. package/src/routes/public/agents.ts +0 -373
  183. package/src/routes/public/channels.ts +0 -191
  184. package/src/routes/public/cli-auth.ts +0 -896
  185. package/src/routes/public/connections.ts +0 -574
  186. package/src/routes/public/landing.ts +0 -16
  187. package/src/routes/public/oauth.ts +0 -147
  188. package/src/routes/public/settings-auth.ts +0 -104
  189. package/src/routes/public/slack.ts +0 -173
  190. package/src/routes/shared/agent-ownership.ts +0 -101
  191. package/src/routes/shared/token-verifier.ts +0 -34
  192. package/src/services/bedrock-model-catalog.ts +0 -217
  193. package/src/services/bedrock-openai-service.ts +0 -658
  194. package/src/services/core-services.ts +0 -1072
  195. package/src/services/image-generation-service.ts +0 -257
  196. package/src/services/instruction-service.ts +0 -318
  197. package/src/services/mcp-registry.ts +0 -94
  198. package/src/services/platform-helpers.ts +0 -287
  199. package/src/services/session-manager.ts +0 -262
  200. package/src/services/settings-resolver.ts +0 -74
  201. package/src/services/system-config-resolver.ts +0 -89
  202. package/src/services/system-skills-service.ts +0 -229
  203. package/src/services/transcription-service.ts +0 -684
  204. package/src/session.ts +0 -110
  205. package/src/spaces/index.ts +0 -1
  206. package/src/spaces/space-resolver.ts +0 -17
  207. package/src/stores/in-memory-agent-store.ts +0 -403
  208. package/src/stores/redis-agent-store.ts +0 -279
  209. package/src/utils/public-url.ts +0 -44
  210. package/src/utils/rate-limiter.ts +0 -94
  211. package/tsconfig.json +0 -33
  212. package/tsconfig.tsbuildinfo +0 -1
@@ -1,1086 +0,0 @@
1
- import { randomUUID } from "node:crypto";
2
- import { createRoute, OpenAPIHono } from "@hono/zod-openapi";
3
- import {
4
- type AgentConfigStore,
5
- createLogger,
6
- createRootSpan,
7
- findTemplateAgentId,
8
- generateWorkerToken,
9
- type InstalledProvider,
10
- type McpServerConfig,
11
- type NetworkConfig,
12
- verifyWorkerToken,
13
- } from "@lobu/core";
14
- import { streamSSE } from "hono/streaming";
15
- import { z } from "zod";
16
- import {
17
- createApiAuthMiddleware,
18
- TOKEN_EXPIRATION_MS,
19
- } from "../../auth/api-auth-middleware";
20
- import type { CliTokenService } from "../../auth/cli/token-service";
21
- import type { ExternalAuthClient } from "../../auth/external/client";
22
- import type { AgentSettingsStore } from "../../auth/settings/agent-settings-store";
23
- import type { QueueProducer } from "../../infrastructure/queue/queue-producer";
24
- import { getModelProviderModules } from "../../modules/module-system";
25
- import type { PlatformRegistry } from "../../platform";
26
- import { resolveAgentOptions } from "../../services/platform-helpers";
27
- import type { ISessionManager, ThreadSession } from "../../session";
28
-
29
- const logger = createLogger("agent-api");
30
-
31
- // =============================================================================
32
- // Constants
33
- // =============================================================================
34
-
35
- const MAX_CONNECTIONS_PER_AGENT = 5;
36
- const MAX_TOTAL_CONNECTIONS = 1000;
37
-
38
- // SSE connection tracking
39
- const sseConnections = new Map<string, Set<any>>();
40
-
41
- // =============================================================================
42
- // Zod Schemas
43
- // =============================================================================
44
-
45
- const NetworkConfigSchema = z.object({
46
- allowedDomains: z.array(z.string()).optional(),
47
- deniedDomains: z.array(z.string()).optional(),
48
- });
49
-
50
- const McpServerConfigSchema = z.object({
51
- url: z.string().optional(),
52
- type: z.enum(["sse", "stdio"]).optional(),
53
- command: z.string().optional(),
54
- args: z.array(z.string()).optional(),
55
- env: z.record(z.string(), z.string()).optional(),
56
- headers: z.record(z.string(), z.string()).optional(),
57
- description: z.string().optional(),
58
- });
59
-
60
- const NixConfigSchema = z.object({
61
- flakeUrl: z.string().optional(),
62
- packages: z.array(z.string()).optional(),
63
- });
64
-
65
- const CreateAgentRequestSchema = z.object({
66
- provider: z.string().default("claude").optional(),
67
- model: z.string().optional(),
68
- agentId: z.string().min(1).optional(),
69
- userId: z.string().min(1).optional(),
70
- thread: z.string().optional(),
71
- forceNew: z.boolean().optional(),
72
- dryRun: z.boolean().optional(),
73
- networkConfig: NetworkConfigSchema.optional(),
74
- mcpServers: z.record(z.string(), McpServerConfigSchema).optional(),
75
- nix: NixConfigSchema.optional(),
76
- });
77
-
78
- const CreateAgentResponseSchema = z.object({
79
- success: z.boolean(),
80
- agentId: z.string(),
81
- token: z.string(),
82
- expiresAt: z.number(),
83
- sseUrl: z.string(),
84
- messagesUrl: z.string(),
85
- });
86
-
87
- const SlackRoutingInfoSchema = z.object({
88
- channel: z.string().describe("Slack channel ID"),
89
- thread: z.string().optional().describe("Thread timestamp for replies"),
90
- team: z.string().optional().describe("Slack team ID"),
91
- });
92
-
93
- const SendMessageRequestSchema = z
94
- .object({
95
- content: z.string().optional().describe("Message content"),
96
- message: z
97
- .string()
98
- .optional()
99
- .describe("Message content (alias for content)"),
100
- messageId: z.string().optional(),
101
- platform: z
102
- .string()
103
- .optional()
104
- .describe("Target platform (api, slack, telegram)"),
105
- slack: SlackRoutingInfoSchema.optional().describe(
106
- "Slack-specific routing info (required when platform=slack)"
107
- ),
108
- })
109
- .passthrough();
110
-
111
- const SendMessageResponseSchema = z.object({
112
- success: z.boolean(),
113
- messageId: z.string(),
114
- agentId: z.string().optional(),
115
- jobId: z.string().optional(),
116
- eventsUrl: z.string().optional(),
117
- queued: z.boolean(),
118
- traceparent: z.string().optional(),
119
- });
120
-
121
- const AgentStatusResponseSchema = z.object({
122
- success: z.boolean(),
123
- agent: z.object({
124
- agentId: z.string(),
125
- userId: z.string(),
126
- status: z.string(),
127
- createdAt: z.number(),
128
- lastActivity: z.number(),
129
- hasActiveConnection: z.boolean(),
130
- }),
131
- });
132
-
133
- const ErrorResponseSchema = z.object({
134
- success: z.boolean(),
135
- error: z.string(),
136
- details: z.string().optional(),
137
- });
138
-
139
- const SuccessResponseSchema = z.object({
140
- success: z.boolean(),
141
- message: z.string().optional(),
142
- agentId: z.string().optional(),
143
- });
144
-
145
- // Path parameters
146
- const AgentIdParamSchema = z.object({
147
- agentId: z.string(),
148
- });
149
-
150
- // =============================================================================
151
- // Validation Helpers
152
- // =============================================================================
153
-
154
- function validateDomainPattern(pattern: string): string | null {
155
- if (!pattern || typeof pattern !== "string") {
156
- return "Domain pattern must be a non-empty string";
157
- }
158
- const trimmed = pattern.trim().toLowerCase();
159
- if (trimmed === "*") return "Bare wildcard '*' is not allowed";
160
- if (trimmed.includes("://"))
161
- return `Domain pattern cannot contain protocol: ${pattern}`;
162
- if (trimmed.includes("/"))
163
- return `Domain pattern cannot contain path: ${pattern}`;
164
- if (trimmed.includes(":") && !trimmed.includes("[")) {
165
- return `Domain pattern cannot contain port: ${pattern}`;
166
- }
167
- if (trimmed.startsWith("*.") || trimmed.startsWith(".")) {
168
- const domain = trimmed.startsWith("*.")
169
- ? trimmed.substring(2)
170
- : trimmed.substring(1);
171
- if (!domain.includes(".")) {
172
- return `Wildcard pattern too broad: ${pattern}`;
173
- }
174
- } else if (!trimmed.includes(".")) {
175
- return `Invalid domain pattern: ${pattern}`;
176
- }
177
- return null;
178
- }
179
-
180
- function validateNetworkConfig(config: NetworkConfig): string | null {
181
- for (const domains of [config.allowedDomains, config.deniedDomains]) {
182
- if (domains) {
183
- for (const domain of domains) {
184
- const error = validateDomainPattern(domain);
185
- if (error) return error;
186
- }
187
- }
188
- }
189
- return null;
190
- }
191
-
192
- function validateMcpServerConfig(
193
- id: string,
194
- config: McpServerConfig
195
- ): string | null {
196
- if (!config.url && !config.command) {
197
- return `MCP ${id}: must specify either 'url' or 'command'`;
198
- }
199
- if (
200
- config.url &&
201
- !config.url.startsWith("http://") &&
202
- !config.url.startsWith("https://")
203
- ) {
204
- return `MCP ${id}: url must be http:// or https://`;
205
- }
206
- if (config.command) {
207
- const dangerousCommands = [
208
- "rm",
209
- "sudo",
210
- "curl",
211
- "wget",
212
- "sh",
213
- "bash",
214
- "zsh",
215
- "kill",
216
- ];
217
- const baseCommand = config.command.split("/").pop()?.split(" ")[0] || "";
218
- if (dangerousCommands.includes(baseCommand)) {
219
- return `MCP ${id}: command '${baseCommand}' is not allowed`;
220
- }
221
- }
222
- return null;
223
- }
224
-
225
- function validateMcpConfig(
226
- mcpServers: Record<string, McpServerConfig>
227
- ): string | null {
228
- for (const [id, config] of Object.entries(mcpServers)) {
229
- if (!/^[a-zA-Z0-9_-]+$/.test(id)) {
230
- return `MCP ID '${id}' is invalid`;
231
- }
232
- const error = validateMcpServerConfig(id, config);
233
- if (error) return error;
234
- }
235
- return null;
236
- }
237
-
238
- // =============================================================================
239
- // Broadcast Functions (exported for use by other modules)
240
- // =============================================================================
241
-
242
- export function broadcastToAgent(
243
- agentId: string,
244
- event: string,
245
- data: unknown
246
- ): void {
247
- const connections = sseConnections.get(agentId);
248
- if (!connections || connections.size === 0) return;
249
-
250
- const deadConnections = new Set<any>();
251
-
252
- for (const res of connections) {
253
- try {
254
- if (res.closed || res.destroyed || res.writableEnded) {
255
- deadConnections.add(res);
256
- continue;
257
- }
258
- if (typeof res.writeSSE === "function") {
259
- res.writeSSE({ event, data: JSON.stringify(data) });
260
- } else if (typeof res.write === "function") {
261
- const message = `event: ${event}\ndata: ${JSON.stringify(data)}\n\n`;
262
- res.write(message);
263
- }
264
- } catch {
265
- deadConnections.add(res);
266
- }
267
- }
268
-
269
- for (const deadRes of deadConnections) {
270
- connections.delete(deadRes);
271
- }
272
- if (connections.size === 0) {
273
- sseConnections.delete(agentId);
274
- }
275
- }
276
-
277
- // =============================================================================
278
- // OpenAPI Route Definitions
279
- // =============================================================================
280
-
281
- const createAgentRoute = createRoute({
282
- method: "post",
283
- path: "/api/v1/agents",
284
- tags: ["Agents"],
285
- summary: "Create a new agent",
286
- security: [{ bearerAuth: [] }],
287
- description:
288
- "Creates a new agent session and returns authentication credentials",
289
- request: {
290
- body: {
291
- content: { "application/json": { schema: CreateAgentRequestSchema } },
292
- },
293
- },
294
- responses: {
295
- 201: {
296
- description: "Agent created",
297
- content: { "application/json": { schema: CreateAgentResponseSchema } },
298
- },
299
- 400: {
300
- description: "Invalid request",
301
- content: { "application/json": { schema: ErrorResponseSchema } },
302
- },
303
- 401: {
304
- description: "Unauthorized",
305
- content: { "application/json": { schema: ErrorResponseSchema } },
306
- },
307
- },
308
- });
309
-
310
- const getAgentRoute = createRoute({
311
- method: "get",
312
- path: "/api/v1/agents/{agentId}",
313
- tags: ["Agents"],
314
- summary: "Get agent status",
315
- security: [{ bearerAuth: [] }],
316
- request: { params: AgentIdParamSchema },
317
- responses: {
318
- 200: {
319
- description: "Agent status",
320
- content: { "application/json": { schema: AgentStatusResponseSchema } },
321
- },
322
- 401: {
323
- description: "Unauthorized",
324
- content: { "application/json": { schema: ErrorResponseSchema } },
325
- },
326
- 404: {
327
- description: "Not found",
328
- content: { "application/json": { schema: ErrorResponseSchema } },
329
- },
330
- },
331
- });
332
-
333
- const deleteAgentRoute = createRoute({
334
- method: "delete",
335
- path: "/api/v1/agents/{agentId}",
336
- tags: ["Agents"],
337
- summary: "Delete an agent",
338
- security: [{ bearerAuth: [] }],
339
- request: { params: AgentIdParamSchema },
340
- responses: {
341
- 200: {
342
- description: "Agent deleted",
343
- content: { "application/json": { schema: SuccessResponseSchema } },
344
- },
345
- 401: {
346
- description: "Unauthorized",
347
- content: { "application/json": { schema: ErrorResponseSchema } },
348
- },
349
- 404: {
350
- description: "Not found",
351
- content: { "application/json": { schema: ErrorResponseSchema } },
352
- },
353
- },
354
- });
355
-
356
- const getAgentEventsRoute = createRoute({
357
- method: "get",
358
- path: "/api/v1/agents/{agentId}/events",
359
- tags: ["Messages"],
360
- summary: "Subscribe to agent events (SSE)",
361
- description: "Server-Sent Events stream for real-time agent updates",
362
- security: [{ bearerAuth: [] }],
363
- request: { params: AgentIdParamSchema },
364
- responses: {
365
- 200: {
366
- description: "SSE stream",
367
- content: { "text/event-stream": { schema: z.string() } },
368
- },
369
- 401: {
370
- description: "Unauthorized",
371
- content: { "application/json": { schema: ErrorResponseSchema } },
372
- },
373
- 429: {
374
- description: "Too many connections",
375
- content: { "application/json": { schema: ErrorResponseSchema } },
376
- },
377
- },
378
- });
379
-
380
- const sendMessageRoute = createRoute({
381
- method: "post",
382
- path: "/api/v1/agents/{agentId}/messages",
383
- tags: ["Messages"],
384
- summary: "Send a message to the agent",
385
- description:
386
- "Send a message to an agent. Supports JSON body or multipart form data for file uploads. " +
387
- "When platform is specified, the message is routed through the platform adapter.",
388
- security: [{ bearerAuth: [] }],
389
- request: {
390
- params: AgentIdParamSchema,
391
- body: {
392
- content: {
393
- "application/json": { schema: SendMessageRequestSchema },
394
- },
395
- },
396
- },
397
- responses: {
398
- 200: {
399
- description: "Message queued",
400
- content: { "application/json": { schema: SendMessageResponseSchema } },
401
- },
402
- 400: {
403
- description: "Invalid request",
404
- content: { "application/json": { schema: ErrorResponseSchema } },
405
- },
406
- 401: {
407
- description: "Unauthorized",
408
- content: { "application/json": { schema: ErrorResponseSchema } },
409
- },
410
- 403: {
411
- description: "Forbidden - worker tokens cannot route to platforms",
412
- content: { "application/json": { schema: ErrorResponseSchema } },
413
- },
414
- 404: {
415
- description: "Agent not found",
416
- content: { "application/json": { schema: ErrorResponseSchema } },
417
- },
418
- },
419
- });
420
-
421
- // =============================================================================
422
- // Create OpenAPI Hono App
423
- // =============================================================================
424
-
425
- export interface AgentApiConfig {
426
- queueProducer: QueueProducer;
427
- sessionManager: ISessionManager;
428
- publicGatewayUrl: string;
429
- adminPassword?: string;
430
- cliTokenService?: CliTokenService;
431
- externalAuthClient?: ExternalAuthClient;
432
- agentSettingsStore?: AgentSettingsStore;
433
- agentConfigStore?: Pick<AgentConfigStore, "getSettings" | "listAgents">;
434
- platformRegistry?: PlatformRegistry;
435
- }
436
-
437
- export function createAgentApi(config: AgentApiConfig): OpenAPIHono;
438
- export function createAgentApi(
439
- queueProducer: QueueProducer,
440
- sessionManager: ISessionManager,
441
- publicGatewayUrl: string
442
- ): OpenAPIHono;
443
- export function createAgentApi(
444
- configOrQueue: AgentApiConfig | QueueProducer,
445
- sessionManager?: ISessionManager,
446
- publicGatewayUrl?: string
447
- ): OpenAPIHono {
448
- const config: AgentApiConfig =
449
- configOrQueue instanceof Object && "queueProducer" in configOrQueue
450
- ? configOrQueue
451
- : {
452
- queueProducer: configOrQueue as QueueProducer,
453
- sessionManager: sessionManager!,
454
- publicGatewayUrl: publicGatewayUrl!,
455
- };
456
-
457
- const {
458
- queueProducer,
459
- adminPassword,
460
- cliTokenService,
461
- agentSettingsStore,
462
- agentConfigStore,
463
- platformRegistry,
464
- } = config;
465
- const sessMgr = config.sessionManager;
466
- const pubUrl = config.publicGatewayUrl;
467
- const app = new OpenAPIHono();
468
-
469
- // Unified auth middleware for all agent API routes
470
- app.use(
471
- "/api/v1/agents/*",
472
- createApiAuthMiddleware({
473
- adminPassword,
474
- cliTokenService,
475
- externalAuthClient: config.externalAuthClient,
476
- allowSettingsSession: true,
477
- })
478
- );
479
-
480
- // =============================================================================
481
- // Route Handlers
482
- // =============================================================================
483
-
484
- // POST /api/v1/agents - Create agent
485
- app.openapi(createAgentRoute, async (c): Promise<any> => {
486
- const body = c.req.valid("json");
487
- const {
488
- provider = "claude",
489
- model,
490
- agentId: requestedAgentId,
491
- userId: requestedUserId,
492
- thread,
493
- forceNew,
494
- dryRun,
495
- networkConfig,
496
- mcpServers,
497
- nix: nixConfig,
498
- } = body;
499
-
500
- // Validate provider
501
- if (provider && !["claude"].includes(provider)) {
502
- return c.json(
503
- { success: false, error: "Invalid provider. Supported: claude" },
504
- 400
505
- );
506
- }
507
-
508
- // Validate network config
509
- if (networkConfig) {
510
- const error = validateNetworkConfig(networkConfig as NetworkConfig);
511
- if (error) return c.json({ success: false, error }, 400);
512
- }
513
-
514
- // Validate MCP config
515
- if (mcpServers) {
516
- const error = validateMcpConfig(
517
- mcpServers as Record<string, McpServerConfig>
518
- );
519
- if (error) return c.json({ success: false, error }, 400);
520
- }
521
-
522
- const isEphemeral = !requestedAgentId?.trim();
523
- const agentId = requestedAgentId?.trim() || randomUUID();
524
-
525
- // For ephemeral agents, auto-provision settings so the worker gets provider config
526
- if (isEphemeral && agentSettingsStore) {
527
- // Try system-key providers first (env var based API keys)
528
- const providerModules = getModelProviderModules();
529
- const systemProviders: InstalledProvider[] = providerModules
530
- .filter((m) => m.hasSystemKey())
531
- .map((m) => ({
532
- providerId: m.providerId,
533
- installedAt: Date.now(),
534
- }));
535
-
536
- if (systemProviders.length > 0) {
537
- // Also inherit pluginsConfig from template agent if available
538
- const templateId = agentConfigStore
539
- ? await findTemplateAgentId(agentConfigStore)
540
- : await agentSettingsStore.findTemplateAgentId();
541
- const templateSettings = templateId
542
- ? await (agentConfigStore?.getSettings(templateId) ??
543
- agentSettingsStore.getSettings(templateId))
544
- : null;
545
- await agentSettingsStore.saveSettings(agentId, {
546
- installedProviders: systemProviders,
547
- pluginsConfig: templateSettings?.pluginsConfig,
548
- });
549
- logger.info(
550
- `Ephemeral agent ${agentId}: provisioned system providers [${systemProviders.map((p) => p.providerId).join(", ")}]`
551
- );
552
- } else {
553
- // Fall back to using an existing agent as template (inherits its providers)
554
- const templateId = agentConfigStore
555
- ? await findTemplateAgentId(agentConfigStore)
556
- : await agentSettingsStore.findTemplateAgentId();
557
- if (templateId) {
558
- const templateSettings = await (agentConfigStore?.getSettings(
559
- templateId
560
- ) ?? agentSettingsStore.getSettings(templateId));
561
- await agentSettingsStore.saveSettings(agentId, {
562
- templateAgentId: templateId,
563
- pluginsConfig: templateSettings?.pluginsConfig,
564
- });
565
- logger.info(
566
- `Ephemeral agent ${agentId}: using template ${templateId}`
567
- );
568
- }
569
- }
570
- }
571
-
572
- const userId = requestedUserId || agentId;
573
-
574
- // Build composite conversationId for user-specific sessions
575
- // Uses _ separator (colons not allowed in BullMQ custom IDs)
576
- const conversationId = thread
577
- ? `${agentId}_${userId}_${thread}`
578
- : `${agentId}_${userId}`;
579
- const channelId = `api_${userId}`;
580
- const deploymentName = `api-${agentId.slice(0, 8)}`;
581
-
582
- // Try to resume existing session (unless forceNew is requested)
583
- if (!forceNew) {
584
- const existing = await sessMgr.getSession(conversationId);
585
- if (existing) {
586
- // Reuse existing session — touch lastActivity and return existing token
587
- await sessMgr.touchSession(conversationId);
588
-
589
- const token = generateWorkerToken(
590
- agentId,
591
- conversationId,
592
- deploymentName,
593
- {
594
- channelId,
595
- agentId,
596
- platform: "api",
597
- sessionKey: userId,
598
- }
599
- );
600
-
601
- const expiresAt = Date.now() + TOKEN_EXPIRATION_MS;
602
- const baseUrl = pubUrl || "http://localhost:8080";
603
-
604
- logger.info(
605
- `Resumed API session: ${conversationId} (agent=${agentId})`
606
- );
607
-
608
- return c.json(
609
- {
610
- success: true,
611
- agentId: conversationId,
612
- token,
613
- expiresAt,
614
- sseUrl: `${baseUrl}/api/v1/agents/${conversationId}/events`,
615
- messagesUrl: `${baseUrl}/api/v1/agents/${conversationId}/messages`,
616
- },
617
- 201
618
- );
619
- }
620
- }
621
-
622
- const token = generateWorkerToken(agentId, conversationId, deploymentName, {
623
- channelId,
624
- agentId,
625
- platform: "api",
626
- sessionKey: userId,
627
- });
628
-
629
- const expiresAt = Date.now() + TOKEN_EXPIRATION_MS;
630
-
631
- const session: ThreadSession = {
632
- conversationId,
633
- channelId,
634
- userId,
635
- threadCreator: userId,
636
- lastActivity: Date.now(),
637
- createdAt: Date.now(),
638
- status: "created",
639
- provider,
640
- model,
641
- networkConfig: networkConfig as NetworkConfig | undefined,
642
- mcpConfig: mcpServers
643
- ? { mcpServers: mcpServers as Record<string, McpServerConfig> }
644
- : undefined,
645
- nixConfig,
646
- agentId,
647
- dryRun: dryRun || false,
648
- };
649
- await sessMgr.setSession(session);
650
-
651
- logger.info(`Created API agent: ${conversationId} (agent=${agentId})`);
652
-
653
- const baseUrl = pubUrl || "http://localhost:8080";
654
- return c.json(
655
- {
656
- success: true,
657
- agentId: conversationId,
658
- token,
659
- expiresAt,
660
- sseUrl: `${baseUrl}/api/v1/agents/${conversationId}/events`,
661
- messagesUrl: `${baseUrl}/api/v1/agents/${conversationId}/messages`,
662
- },
663
- 201
664
- );
665
- });
666
-
667
- // GET /api/v1/agents/:agentId - Get status
668
- app.openapi(getAgentRoute, async (c): Promise<any> => {
669
- const { agentId: sessionKey } = c.req.valid("param");
670
-
671
- const session = await sessMgr.getSession(sessionKey);
672
- if (!session) {
673
- return c.json({ success: false, error: "Agent not found" }, 404);
674
- }
675
-
676
- const hasActiveConnection =
677
- sseConnections.has(sessionKey) &&
678
- (sseConnections.get(sessionKey)?.size ?? 0) > 0;
679
-
680
- return c.json({
681
- success: true,
682
- agent: {
683
- agentId: session.conversationId,
684
- userId: session.userId,
685
- status: session.status || "active",
686
- createdAt: session.createdAt,
687
- lastActivity: session.lastActivity,
688
- hasActiveConnection,
689
- },
690
- });
691
- });
692
-
693
- // DELETE /api/v1/agents/:agentId
694
- app.openapi(deleteAgentRoute, async (c): Promise<any> => {
695
- const { agentId: sessionKey } = c.req.valid("param");
696
-
697
- const connections = sseConnections.get(sessionKey);
698
- if (connections) {
699
- for (const connection of connections) {
700
- try {
701
- if (typeof connection.writeSSE === "function") {
702
- connection.writeSSE({
703
- event: "closed",
704
- data: JSON.stringify({ reason: "agent_deleted" }),
705
- });
706
- } else if (typeof connection.write === "function") {
707
- connection.write(
708
- `event: closed\ndata: ${JSON.stringify({ reason: "agent_deleted" })}\n\n`
709
- );
710
- }
711
- connection.close?.();
712
- connection.end?.();
713
- } catch {
714
- // Ignore
715
- }
716
- }
717
- sseConnections.delete(sessionKey);
718
- }
719
-
720
- // Get real agentId from session before deleting
721
- const session = await sessMgr.getSession(sessionKey);
722
- const realAgentId = session?.agentId || sessionKey;
723
-
724
- await sessMgr.deleteSession(sessionKey);
725
- // Clean up ephemeral agent settings
726
- if (agentSettingsStore) {
727
- await agentSettingsStore.deleteSettings(realAgentId).catch(() => {
728
- /* best-effort cleanup */
729
- });
730
- }
731
- logger.info(`Deleted agent ${sessionKey}`);
732
-
733
- return c.json({
734
- success: true,
735
- message: "Agent deleted",
736
- agentId: sessionKey,
737
- });
738
- });
739
-
740
- // GET /api/v1/agents/:agentId/events - SSE stream
741
- app.openapi(getAgentEventsRoute, async (c): Promise<any> => {
742
- const { agentId: sessionKey } = c.req.valid("param");
743
-
744
- const session = await sessMgr.getSession(sessionKey);
745
- if (!session) {
746
- return c.json({ success: false, error: "Agent not found" }, 404);
747
- }
748
-
749
- // Check connection limits
750
- const totalConnections = Array.from(sseConnections.values()).reduce(
751
- (acc, set) => acc + set.size,
752
- 0
753
- );
754
- if (totalConnections >= MAX_TOTAL_CONNECTIONS) {
755
- return c.json(
756
- { success: false, error: "Server connection limit reached" },
757
- 429
758
- );
759
- }
760
-
761
- // Use conversationId as the SSE connection key (matches broadcastToAgent calls)
762
- const sseKey = session.conversationId;
763
- if (!sseConnections.has(sseKey)) {
764
- sseConnections.set(sseKey, new Set());
765
- }
766
- const agentConnections = sseConnections.get(sseKey)!;
767
- if (agentConnections.size >= MAX_CONNECTIONS_PER_AGENT) {
768
- return c.json(
769
- {
770
- success: false,
771
- error: `Maximum ${MAX_CONNECTIONS_PER_AGENT} connections`,
772
- },
773
- 429
774
- );
775
- }
776
-
777
- // Return SSE stream
778
- return streamSSE(c, async (stream) => {
779
- agentConnections.add(stream);
780
-
781
- await stream.writeSSE({
782
- event: "connected",
783
- data: JSON.stringify({
784
- agentId: session.agentId || sessionKey,
785
- timestamp: Date.now(),
786
- }),
787
- });
788
-
789
- const heartbeatInterval = setInterval(async () => {
790
- try {
791
- await stream.writeSSE({
792
- event: "ping",
793
- data: JSON.stringify({ timestamp: Date.now() }),
794
- });
795
- } catch {
796
- clearInterval(heartbeatInterval);
797
- }
798
- }, 30000);
799
-
800
- stream.onAbort(() => {
801
- clearInterval(heartbeatInterval);
802
- agentConnections.delete(stream);
803
- if (agentConnections.size === 0) {
804
- sseConnections.delete(sseKey);
805
- }
806
- logger.info(`SSE connection closed for session ${sseKey}`);
807
- });
808
-
809
- while (true) {
810
- await stream.sleep(1000);
811
- }
812
- });
813
- });
814
-
815
- // POST /api/v1/agents/:agentId/messages - Send message
816
- // Supports two paths:
817
- // 1. Direct API (no platform field): requires pre-created session, enqueues directly
818
- // 2. Platform-routed (platform field present): delegates to platform adapter
819
- app.openapi(sendMessageRoute, async (c): Promise<any> => {
820
- const { agentId } = c.req.valid("param");
821
-
822
- // Parse body — multipart for file uploads, JSON otherwise
823
- const contentType = c.req.header("content-type") || "";
824
- let body: Record<string, any>;
825
- let files: Array<{ buffer: Buffer; filename: string }> | undefined;
826
-
827
- if (contentType.includes("multipart/form-data")) {
828
- const formData = await c.req.formData();
829
- body = {
830
- content: formData.get("content") as string | null,
831
- message: formData.get("message") as string | null,
832
- messageId: formData.get("messageId") as string | null,
833
- platform: formData.get("platform") as string | null,
834
- };
835
-
836
- // Extract nested platform routing from form fields
837
- const slackChannel = formData.get("slack.channel") as string;
838
- if (slackChannel) {
839
- body.slack = {
840
- channel: slackChannel,
841
- thread: formData.get("slack.thread") as string | undefined,
842
- team: formData.get("slack.team") as string | undefined,
843
- };
844
- }
845
- const whatsappChat = formData.get("whatsapp.chat") as string;
846
- if (whatsappChat) {
847
- body.whatsapp = { chat: whatsappChat };
848
- }
849
- const telegramChatId = formData.get("telegram.chatId") as string;
850
- if (telegramChatId) {
851
- body.telegram = { chatId: telegramChatId };
852
- }
853
-
854
- // Extract files with size validation
855
- const MAX_FILE_SIZE = 50 * 1024 * 1024;
856
- const MAX_TOTAL_SIZE = 100 * 1024 * 1024;
857
- const MAX_FILE_COUNT = 10;
858
- const fileEntries = formData.getAll("files");
859
- if (fileEntries.length > MAX_FILE_COUNT) {
860
- return c.json(
861
- {
862
- success: false,
863
- error: `Too many files: ${fileEntries.length} (max ${MAX_FILE_COUNT})`,
864
- },
865
- 400
866
- );
867
- }
868
- if (fileEntries.length > 0) {
869
- const fileResults: Array<{ buffer: Buffer; filename: string }> = [];
870
- let totalSize = 0;
871
- for (const entry of fileEntries) {
872
- if (entry instanceof File) {
873
- if (entry.size > MAX_FILE_SIZE) {
874
- return c.json(
875
- {
876
- success: false,
877
- error: `File "${entry.name}" exceeds maximum size of ${MAX_FILE_SIZE / 1024 / 1024}MB`,
878
- },
879
- 400
880
- );
881
- }
882
- totalSize += entry.size;
883
- if (totalSize > MAX_TOTAL_SIZE) {
884
- return c.json(
885
- {
886
- success: false,
887
- error: `Total upload size exceeds maximum of ${MAX_TOTAL_SIZE / 1024 / 1024}MB`,
888
- },
889
- 400
890
- );
891
- }
892
- const arrayBuffer = await entry.arrayBuffer();
893
- fileResults.push({
894
- buffer: Buffer.from(arrayBuffer),
895
- filename: entry.name,
896
- });
897
- }
898
- }
899
- if (fileResults.length > 0) files = fileResults;
900
- }
901
- } else {
902
- body = c.req.valid("json");
903
- }
904
-
905
- const messageContent = body.content || body.message;
906
- const messageId = body.messageId || randomUUID();
907
-
908
- if (!messageContent || typeof messageContent !== "string") {
909
- return c.json({ success: false, error: "content is required" }, 400);
910
- }
911
-
912
- const platform = body.platform as string | undefined;
913
-
914
- // ── Platform-routed path ──────────────────────────────────────────────────
915
- // When platform is specified, delegate to the platform adapter which handles
916
- // session creation, routing, and file delivery.
917
- if (platform) {
918
- // Worker tokens cannot route to user-facing platform connections
919
- const authHeader = c.req.header("Authorization");
920
- const rawToken = authHeader?.startsWith("Bearer ")
921
- ? authHeader.substring(7)
922
- : "";
923
- if (verifyWorkerToken(rawToken)) {
924
- return c.json(
925
- { success: false, error: "Worker tokens cannot route to platforms" },
926
- 403
927
- );
928
- }
929
-
930
- if (!platformRegistry) {
931
- return c.json(
932
- { success: false, error: "Platform routing not available" },
933
- 501
934
- );
935
- }
936
-
937
- const adapter = platformRegistry.get(platform);
938
- if (!adapter) {
939
- return c.json(
940
- {
941
- success: false,
942
- error: `Platform "${platform}" not found`,
943
- details: `Available: ${platformRegistry.getAvailablePlatforms().join(", ")}`,
944
- },
945
- 404
946
- );
947
- }
948
-
949
- if (!adapter.sendMessage) {
950
- return c.json(
951
- {
952
- success: false,
953
- error: `Platform "${platform}" does not support sendMessage`,
954
- },
955
- 501
956
- );
957
- }
958
-
959
- // Extract platform-specific routing info
960
- let channelId = agentId;
961
- let conversationId: string | undefined =
962
- platform === "api" ? agentId : undefined;
963
- let teamId = "api";
964
-
965
- if (adapter.extractRoutingInfo) {
966
- const routingInfo = adapter.extractRoutingInfo(
967
- body as Record<string, unknown>
968
- );
969
- if (routingInfo) {
970
- channelId = routingInfo.channelId;
971
- conversationId = routingInfo.conversationId || conversationId;
972
- teamId = routingInfo.teamId || "api";
973
- } else if (platform !== "api") {
974
- return c.json(
975
- {
976
- success: false,
977
- error: `Platform-specific routing info required for ${platform}`,
978
- },
979
- 400
980
- );
981
- }
982
- }
983
-
984
- logger.info(
985
- `Sending message via ${platform}: agentId=${agentId}, channelId=${channelId}${files?.length ? `, files=${files.length}` : ""}`
986
- );
987
-
988
- try {
989
- const result = await adapter.sendMessage(rawToken, messageContent, {
990
- agentId,
991
- channelId,
992
- conversationId,
993
- teamId,
994
- files,
995
- });
996
-
997
- return c.json({
998
- success: true,
999
- agentId,
1000
- messageId: result.messageId,
1001
- eventsUrl: result.eventsUrl,
1002
- queued: result.queued || false,
1003
- });
1004
- } catch (error) {
1005
- logger.error("Failed to send platform message", { error });
1006
- return c.json({ success: false, error: "Internal server error" }, 500);
1007
- }
1008
- }
1009
-
1010
- // ── Direct API path ───────────────────────────────────────────────────────
1011
- // No platform field: use existing session-based direct enqueue
1012
- const session = await sessMgr.getSession(agentId);
1013
- if (!session) {
1014
- return c.json({ success: false, error: "Agent not found" }, 404);
1015
- }
1016
-
1017
- await sessMgr.touchSession(agentId);
1018
-
1019
- const realAgentId = session.agentId || agentId;
1020
-
1021
- const { span: rootSpan, traceparent } = createRootSpan("message_received", {
1022
- "lobu.agent_id": realAgentId,
1023
- "lobu.message_id": messageId,
1024
- });
1025
-
1026
- try {
1027
- const channelId = session.channelId || `api_${session.userId}`;
1028
-
1029
- const baseOptions: Record<string, any> = {
1030
- provider: session.provider || "claude",
1031
- model: session.model,
1032
- };
1033
- const agentOptions = await resolveAgentOptions(
1034
- realAgentId,
1035
- baseOptions,
1036
- agentSettingsStore
1037
- );
1038
-
1039
- const {
1040
- networkConfig: settingsNetwork,
1041
- mcpServers: settingsMcpServers,
1042
- ...remainingOptions
1043
- } = agentOptions;
1044
-
1045
- const jobId = await queueProducer.enqueueMessage({
1046
- userId: session.userId,
1047
- conversationId: session.conversationId || agentId,
1048
- messageId,
1049
- channelId,
1050
- teamId: "api",
1051
- agentId: realAgentId,
1052
- botId: "lobu-api",
1053
- platform: "api",
1054
- messageText: messageContent,
1055
- platformMetadata: {
1056
- agentId: realAgentId,
1057
- source: "direct-api",
1058
- traceparent: traceparent || undefined,
1059
- dryRun: session.dryRun || false,
1060
- },
1061
- agentOptions: remainingOptions,
1062
- networkConfig: session.networkConfig || settingsNetwork,
1063
- mcpConfig:
1064
- session.mcpConfig ||
1065
- (settingsMcpServers ? { mcpServers: settingsMcpServers } : undefined),
1066
- });
1067
-
1068
- rootSpan?.end();
1069
-
1070
- return c.json({
1071
- success: true,
1072
- messageId,
1073
- jobId,
1074
- queued: true,
1075
- traceparent: traceparent || undefined,
1076
- });
1077
- } catch (error) {
1078
- rootSpan?.end();
1079
- throw error;
1080
- }
1081
- });
1082
-
1083
- logger.debug("Hono Agent API routes registered");
1084
-
1085
- return app;
1086
- }