@red-hat-developer-hub/backstage-plugin-intelligent-assistant-backend 3.0.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.
Files changed (51) hide show
  1. package/CHANGELOG.md +607 -0
  2. package/README.md +400 -0
  3. package/app-config.yaml +25 -0
  4. package/config.d.ts +142 -0
  5. package/dist/database/migration.cjs.js +19 -0
  6. package/dist/database/migration.cjs.js.map +1 -0
  7. package/dist/index.cjs.js +12 -0
  8. package/dist/index.cjs.js.map +1 -0
  9. package/dist/index.d.ts +73 -0
  10. package/dist/plugin.cjs.js +95 -0
  11. package/dist/plugin.cjs.js.map +1 -0
  12. package/dist/service/constant.cjs.js +107 -0
  13. package/dist/service/constant.cjs.js.map +1 -0
  14. package/dist/service/mcp-server-store.cjs.js +107 -0
  15. package/dist/service/mcp-server-store.cjs.js.map +1 -0
  16. package/dist/service/mcp-server-validator.cjs.js +215 -0
  17. package/dist/service/mcp-server-validator.cjs.js.map +1 -0
  18. package/dist/service/middleware/createPermissionMiddleware.cjs.js +24 -0
  19. package/dist/service/middleware/createPermissionMiddleware.cjs.js.map +1 -0
  20. package/dist/service/middleware/createRateLimitMiddleware.cjs.js +52 -0
  21. package/dist/service/middleware/createRateLimitMiddleware.cjs.js.map +1 -0
  22. package/dist/service/middleware/getIdentity.cjs.js +39 -0
  23. package/dist/service/middleware/getIdentity.cjs.js.map +1 -0
  24. package/dist/service/notebooks/VectorStoresOperator.cjs.js +347 -0
  25. package/dist/service/notebooks/VectorStoresOperator.cjs.js.map +1 -0
  26. package/dist/service/notebooks/documents/documentHelpers.cjs.js +36 -0
  27. package/dist/service/notebooks/documents/documentHelpers.cjs.js.map +1 -0
  28. package/dist/service/notebooks/documents/documentService.cjs.js +189 -0
  29. package/dist/service/notebooks/documents/documentService.cjs.js.map +1 -0
  30. package/dist/service/notebooks/documents/fileParser.cjs.js +139 -0
  31. package/dist/service/notebooks/documents/fileParser.cjs.js.map +1 -0
  32. package/dist/service/notebooks/notebooksRouters.cjs.js +477 -0
  33. package/dist/service/notebooks/notebooksRouters.cjs.js.map +1 -0
  34. package/dist/service/notebooks/sessions/sessionService.cjs.js +226 -0
  35. package/dist/service/notebooks/sessions/sessionService.cjs.js.map +1 -0
  36. package/dist/service/notebooks/types/notebooksTypes.cjs.js +40 -0
  37. package/dist/service/notebooks/types/notebooksTypes.cjs.js.map +1 -0
  38. package/dist/service/notebooks/utils.cjs.js +70 -0
  39. package/dist/service/notebooks/utils.cjs.js.map +1 -0
  40. package/dist/service/permission.cjs.js +26 -0
  41. package/dist/service/permission.cjs.js.map +1 -0
  42. package/dist/service/router.cjs.js +638 -0
  43. package/dist/service/router.cjs.js.map +1 -0
  44. package/dist/service/token-encryption.cjs.js +101 -0
  45. package/dist/service/token-encryption.cjs.js.map +1 -0
  46. package/dist/service/utils.cjs.js +40 -0
  47. package/dist/service/utils.cjs.js.map +1 -0
  48. package/dist/service/validation.cjs.js +45 -0
  49. package/dist/service/validation.cjs.js.map +1 -0
  50. package/migrations/20260302120000_add_mcp_servers.js +41 -0
  51. package/package.json +109 -0
@@ -0,0 +1,638 @@
1
+ 'use strict';
2
+
3
+ var rootHttpRouter = require('@backstage/backend-defaults/rootHttpRouter');
4
+ var errors = require('@backstage/errors');
5
+ var pluginPermissionNode = require('@backstage/plugin-permission-node');
6
+ var express = require('express');
7
+ var httpProxyMiddleware = require('http-proxy-middleware');
8
+ var backstagePluginIntelligentAssistantCommon = require('@red-hat-developer-hub/backstage-plugin-intelligent-assistant-common');
9
+ var node_stream = require('node:stream');
10
+ var constant = require('./constant.cjs.js');
11
+ var mcpServerStore = require('./mcp-server-store.cjs.js');
12
+ var mcpServerValidator = require('./mcp-server-validator.cjs.js');
13
+ var createPermissionMiddleware = require('./middleware/createPermissionMiddleware.cjs.js');
14
+ var createRateLimitMiddleware = require('./middleware/createRateLimitMiddleware.cjs.js');
15
+ var getIdentity = require('./middleware/getIdentity.cjs.js');
16
+ var VectorStoresOperator = require('./notebooks/VectorStoresOperator.cjs.js');
17
+ var permission = require('./permission.cjs.js');
18
+ var tokenEncryption = require('./token-encryption.cjs.js');
19
+ var utils = require('./utils.cjs.js');
20
+ var validation = require('./validation.cjs.js');
21
+
22
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
23
+
24
+ var express__default = /*#__PURE__*/_interopDefaultCompat(express);
25
+
26
+ async function buildMcpHeaders(servers, store, userEntityRef, options) {
27
+ const headers = {};
28
+ const userSettings = await store.listByUser(userEntityRef);
29
+ const settingsMap = new Map(userSettings.map((s) => [s.server_name, s]));
30
+ for (const server of servers) {
31
+ const setting = settingsMap.get(server.name);
32
+ const enabled = setting ? Boolean(setting.enabled) : true;
33
+ if (!enabled) continue;
34
+ if (server.auth === "dcr") {
35
+ if (options?.dcrAuth) {
36
+ try {
37
+ const { token } = await options.dcrAuth.authService.getPluginRequestToken({
38
+ onBehalfOf: options.dcrAuth.credentials,
39
+ targetPluginId: "mcp-actions"
40
+ });
41
+ headers[server.name] = { Authorization: `${token}` };
42
+ } catch (err) {
43
+ options?.logger?.error(
44
+ `Failed to mint DCR token for MCP server '${server.name}': ${err}`
45
+ );
46
+ }
47
+ } else {
48
+ options?.logger?.warn(
49
+ `MCP server '${server.name}' has auth: dcr but AuthService is not available; skipping`
50
+ );
51
+ }
52
+ } else {
53
+ const token = setting?.token || server.token;
54
+ if (token) {
55
+ headers[server.name] = { Authorization: `${token}` };
56
+ }
57
+ }
58
+ }
59
+ return Object.keys(headers).length > 0 ? JSON.stringify(headers) : "";
60
+ }
61
+ async function createRouter(options) {
62
+ const { logger, config, database, httpAuth, auth, userInfo, permissions } = options;
63
+ const router = express.Router();
64
+ router.use(express__default.default.json());
65
+ const port = config.getOptionalNumber("intelligent-assistant.servicePort") ?? constant.DEFAULT_LIGHTSPEED_SERVICE_PORT;
66
+ const system_prompt = config.getOptionalString(
67
+ "intelligent-assistant.systemPrompt"
68
+ );
69
+ const lcsBaseUrl = `http://${constant.DEFAULT_LIGHTSPEED_SERVICE_HOST}:${port}`;
70
+ const apiProxy = httpProxyMiddleware.createProxyMiddleware({
71
+ target: lcsBaseUrl,
72
+ changeOrigin: true,
73
+ pathRewrite: (path, req) => {
74
+ const userEntityRef = req.userEntityRef;
75
+ const newPath = utils.rewriteProxyPath(path, userEntityRef);
76
+ if (newPath !== path) {
77
+ logger.info(`Rewriting path from ${path} to ${newPath}`);
78
+ }
79
+ return newPath;
80
+ }
81
+ });
82
+ const vectorStoresOperator = VectorStoresOperator.VectorStoresOperator.getInstance(
83
+ lcsBaseUrl,
84
+ logger
85
+ );
86
+ const mcpServersConfig = config.getOptionalConfigArray(
87
+ "intelligent-assistant.mcpServers"
88
+ );
89
+ const staticServers = [];
90
+ if (mcpServersConfig) {
91
+ for (const mcpServer of mcpServersConfig) {
92
+ const authValue = mcpServer.getOptionalString("auth")?.toLowerCase();
93
+ if (authValue && authValue !== "dcr") {
94
+ logger.warn(
95
+ `MCP server '${mcpServer.getString("name")}' has unsupported auth value '${authValue}'; only 'dcr' is supported \u2014 falling back to static-token mode`
96
+ );
97
+ }
98
+ staticServers.push({
99
+ name: mcpServer.getString("name"),
100
+ token: mcpServer.getOptionalString("token"),
101
+ auth: authValue === "dcr" ? "dcr" : void 0
102
+ });
103
+ }
104
+ }
105
+ const dbClient = await database.getClient();
106
+ const encryptor = tokenEncryption.createTokenEncryptor(config, logger);
107
+ const settingsStore = new mcpServerStore.McpUserSettingsStore(dbClient, encryptor, logger);
108
+ const mcpValidator = new mcpServerValidator.McpServerValidator(logger);
109
+ const lcsUrlCache = /* @__PURE__ */ new Map();
110
+ async function refreshLcsUrlCache() {
111
+ try {
112
+ const response = await fetch(`${lcsBaseUrl}/v1/mcp-servers`, {
113
+ signal: AbortSignal.timeout(5e3)
114
+ });
115
+ if (!response.ok) {
116
+ logger.warn(
117
+ `Failed to fetch MCP server URLs from LCS: HTTP ${response.status}`
118
+ );
119
+ return;
120
+ }
121
+ const data = await response.json();
122
+ for (const s of data.servers) {
123
+ lcsUrlCache.set(s.name, s.url);
124
+ }
125
+ logger.info(`Cached ${lcsUrlCache.size} MCP server URL(s) from LCS`);
126
+ } catch (error) {
127
+ const msg = error instanceof Error ? error.message : String(error);
128
+ logger.warn(`Failed to fetch MCP server URLs from LCS: ${msg}`);
129
+ }
130
+ }
131
+ function resolveServerUrl(serverName) {
132
+ return lcsUrlCache.get(serverName);
133
+ }
134
+ refreshLcsUrlCache().catch(() => {
135
+ });
136
+ router.get("/health", (_, response) => {
137
+ response.json({ status: "ok" });
138
+ });
139
+ const permissionIntegrationRouter = pluginPermissionNode.createPermissionIntegrationRouter({
140
+ permissions: backstagePluginIntelligentAssistantCommon.lightspeedPermissions
141
+ });
142
+ router.use(permissionIntegrationRouter);
143
+ const identityMiddleware = getIdentity.createIdentityMiddleware(
144
+ httpAuth,
145
+ userInfo,
146
+ logger
147
+ );
148
+ router.use((req, res, next) => {
149
+ if (req.path.startsWith("/notebooks")) {
150
+ return next();
151
+ }
152
+ return identityMiddleware(req, res, next);
153
+ });
154
+ const authorizer = permission.userPermissionAuthorization(permissions);
155
+ function requirePermission(permission) {
156
+ return createPermissionMiddleware.createPermissionMiddleware(authorizer, permission, logger);
157
+ }
158
+ const expensiveRateLimiter = createRateLimitMiddleware.createRateLimitMiddleware(
159
+ config,
160
+ "expensive",
161
+ logger
162
+ );
163
+ const generalRateLimiter = createRateLimitMiddleware.createRateLimitMiddleware(
164
+ config,
165
+ "general",
166
+ logger
167
+ );
168
+ router.get(
169
+ "/mcp-servers",
170
+ generalRateLimiter,
171
+ requirePermission(backstagePluginIntelligentAssistantCommon.lightspeedMcpReadPermission),
172
+ async (req, res) => {
173
+ try {
174
+ const { userEntityRef } = getIdentity.getIdentity(req);
175
+ const userSettings = await settingsStore.listByUser(userEntityRef);
176
+ const settingsMap = new Map(userSettings.map((s) => [s.server_name, s]));
177
+ const hasAllUrls = staticServers.every((s) => lcsUrlCache.has(s.name));
178
+ if (!hasAllUrls) {
179
+ await refreshLcsUrlCache();
180
+ }
181
+ const servers = staticServers.map((server) => {
182
+ const setting = settingsMap.get(server.name);
183
+ return {
184
+ name: server.name,
185
+ url: resolveServerUrl(server.name),
186
+ enabled: setting ? Boolean(setting.enabled) : true,
187
+ status: setting?.status ?? "unknown",
188
+ toolCount: setting?.tool_count ?? 0,
189
+ // DCR servers always have a token (minted per-request); static servers check DB + config.
190
+ hasToken: server.auth === "dcr" || !!(setting?.token || server.token),
191
+ hasUserToken: !!setting?.token,
192
+ auth: server.auth
193
+ };
194
+ });
195
+ res.json({ servers });
196
+ } catch (error) {
197
+ logger.error(`Error listing MCP servers: ${error}`);
198
+ res.status(500).json({ error: "Failed to list MCP servers" });
199
+ }
200
+ }
201
+ );
202
+ router.post(
203
+ "/mcp-servers/validate",
204
+ generalRateLimiter,
205
+ requirePermission(backstagePluginIntelligentAssistantCommon.lightspeedMcpReadPermission),
206
+ async (req, res) => {
207
+ try {
208
+ const { url, token } = req.body;
209
+ if (!url || !token) {
210
+ res.status(400).json({ error: "url and token are required" });
211
+ return;
212
+ }
213
+ let knownUrls = new Set(lcsUrlCache.values());
214
+ if (!knownUrls.has(url)) {
215
+ await refreshLcsUrlCache();
216
+ knownUrls = new Set(lcsUrlCache.values());
217
+ }
218
+ if (!knownUrls.has(url)) {
219
+ res.status(400).json({
220
+ error: "URL not recognized \u2014 only MCP server URLs registered in LCS are allowed"
221
+ });
222
+ return;
223
+ }
224
+ const result = await mcpValidator.validate(url, token);
225
+ res.json(result);
226
+ } catch (error) {
227
+ logger.error(`Error validating MCP credentials: ${error}`);
228
+ res.status(500).json({ error: "Validation failed" });
229
+ }
230
+ }
231
+ );
232
+ router.post(
233
+ "/mcp-servers/:name/validate",
234
+ generalRateLimiter,
235
+ requirePermission(backstagePluginIntelligentAssistantCommon.lightspeedMcpManagePermission),
236
+ async (req, res) => {
237
+ try {
238
+ const { userEntityRef, credentials } = getIdentity.getIdentity(req);
239
+ const { name } = req.params;
240
+ const server = staticServers.find((s) => s.name === name);
241
+ if (!server) {
242
+ res.status(404).json({
243
+ error: `MCP server '${name}' is not configured \u2014 it must be defined in the Lightspeed Stack config and listed under intelligent-assistant.mcpServers in app-config`
244
+ });
245
+ return;
246
+ }
247
+ let serverUrl = resolveServerUrl(server.name);
248
+ if (!serverUrl) {
249
+ await refreshLcsUrlCache();
250
+ serverUrl = resolveServerUrl(server.name);
251
+ }
252
+ if (!serverUrl) {
253
+ res.status(400).json({ error: "Server has no URL \u2014 not found in LCS or config" });
254
+ return;
255
+ }
256
+ let effectiveToken;
257
+ if (server.auth === "dcr") {
258
+ try {
259
+ const { token: dcrToken } = await auth.getPluginRequestToken({
260
+ onBehalfOf: credentials,
261
+ targetPluginId: "mcp-actions"
262
+ });
263
+ effectiveToken = dcrToken;
264
+ } catch (err) {
265
+ logger.error(
266
+ `Failed to mint DCR token for server '${name}': ${err}`
267
+ );
268
+ res.status(502).json({
269
+ error: `Failed to mint authentication token for DCR server '${name}' \u2014 check Backstage auth configuration`
270
+ });
271
+ return;
272
+ }
273
+ } else {
274
+ const setting = await settingsStore.get(name, userEntityRef);
275
+ effectiveToken = setting?.token || server.token;
276
+ }
277
+ if (!effectiveToken) {
278
+ res.status(400).json({ error: "No token available \u2014 provide one first" });
279
+ return;
280
+ }
281
+ const validation = await mcpValidator.validate(
282
+ serverUrl,
283
+ effectiveToken
284
+ );
285
+ const status = validation.valid ? "connected" : "error";
286
+ await settingsStore.updateStatus(
287
+ name,
288
+ userEntityRef,
289
+ status,
290
+ validation.toolCount
291
+ );
292
+ res.json({
293
+ name,
294
+ status,
295
+ toolCount: validation.toolCount,
296
+ validation
297
+ });
298
+ } catch (error) {
299
+ logger.error(`Error validating MCP server: ${error}`);
300
+ res.status(500).json({ error: "Validation failed" });
301
+ }
302
+ }
303
+ );
304
+ router.patch(
305
+ "/mcp-servers/:name",
306
+ generalRateLimiter,
307
+ requirePermission(backstagePluginIntelligentAssistantCommon.lightspeedMcpManagePermission),
308
+ async (req, res) => {
309
+ try {
310
+ const { userEntityRef } = getIdentity.getIdentity(req);
311
+ const { name } = req.params;
312
+ const server = staticServers.find((s) => s.name === name);
313
+ if (!server) {
314
+ res.status(404).json({
315
+ error: `MCP server '${name}' is not configured \u2014 it must be defined in the Lightspeed Stack config and listed under intelligent-assistant.mcpServers in app-config`
316
+ });
317
+ return;
318
+ }
319
+ const body = req.body ?? {};
320
+ const hasEnabledField = Object.prototype.hasOwnProperty.call(
321
+ body,
322
+ "enabled"
323
+ );
324
+ const hasTokenField = Object.prototype.hasOwnProperty.call(
325
+ body,
326
+ "token"
327
+ );
328
+ const { enabled, token } = body;
329
+ if (!hasEnabledField && !hasTokenField) {
330
+ res.status(400).json({
331
+ error: "At least one of enabled or token must be provided"
332
+ });
333
+ return;
334
+ }
335
+ const setting = await settingsStore.upsert(name, userEntityRef, {
336
+ enabled: hasEnabledField ? enabled : void 0,
337
+ token: hasTokenField ? token : void 0
338
+ });
339
+ let validation;
340
+ let serverUrl = resolveServerUrl(server.name);
341
+ if (token && !serverUrl) {
342
+ await refreshLcsUrlCache();
343
+ serverUrl = resolveServerUrl(server.name);
344
+ }
345
+ if (token && serverUrl) {
346
+ validation = await mcpValidator.validate(serverUrl, token);
347
+ const newStatus = validation.valid ? "connected" : "error";
348
+ await settingsStore.updateStatus(
349
+ name,
350
+ userEntityRef,
351
+ newStatus,
352
+ validation.toolCount
353
+ );
354
+ setting.status = newStatus;
355
+ setting.tool_count = validation.toolCount;
356
+ }
357
+ const result = {
358
+ server: {
359
+ name: server.name,
360
+ url: resolveServerUrl(server.name),
361
+ enabled: Boolean(setting.enabled),
362
+ status: setting.status,
363
+ toolCount: setting.tool_count,
364
+ hasToken: server.auth === "dcr" || !!(setting.token || server.token),
365
+ hasUserToken: !!setting.token,
366
+ auth: server.auth
367
+ }
368
+ };
369
+ if (validation) result.validation = validation;
370
+ res.json(result);
371
+ } catch (error) {
372
+ logger.error(`Error updating MCP server settings: ${error}`);
373
+ res.status(500).json({ error: "Failed to update MCP server settings" });
374
+ }
375
+ }
376
+ );
377
+ router.get(
378
+ "/notebook-conversation-ids",
379
+ generalRateLimiter,
380
+ async (req, res) => {
381
+ try {
382
+ const { userEntityRef } = getIdentity.getIdentity(req);
383
+ const vectorStoresPage = await vectorStoresOperator.vectorStores.list();
384
+ const vectorStores = vectorStoresPage.data || [];
385
+ const conversationIds = [];
386
+ for (const store of vectorStores) {
387
+ const sessionUserId = store.metadata?.user_id;
388
+ const conversationId = store.metadata?.conversation_id;
389
+ if (sessionUserId === userEntityRef && conversationId) {
390
+ conversationIds.push(conversationId);
391
+ }
392
+ }
393
+ res.json({
394
+ conversation_ids: conversationIds
395
+ });
396
+ } catch (error) {
397
+ const errormsg = `Error fetching notebook conversation IDs: ${error}`;
398
+ logger.error(errormsg);
399
+ if (error instanceof errors.NotAllowedError) {
400
+ res.status(403).json({ error: error.message });
401
+ } else {
402
+ res.status(500).json({ error: errormsg });
403
+ }
404
+ }
405
+ }
406
+ );
407
+ router.get(
408
+ "/v1/models",
409
+ generalRateLimiter,
410
+ requirePermission(backstagePluginIntelligentAssistantCommon.lightspeedChatReadPermission),
411
+ apiProxy
412
+ );
413
+ router.get(
414
+ "/v1/shields",
415
+ generalRateLimiter,
416
+ requirePermission(backstagePluginIntelligentAssistantCommon.lightspeedChatReadPermission),
417
+ apiProxy
418
+ );
419
+ router.get(
420
+ "/v2/conversations",
421
+ generalRateLimiter,
422
+ requirePermission(backstagePluginIntelligentAssistantCommon.lightspeedChatReadPermission),
423
+ apiProxy
424
+ );
425
+ router.get(
426
+ "/v2/conversations/:conversation_id",
427
+ generalRateLimiter,
428
+ requirePermission(backstagePluginIntelligentAssistantCommon.lightspeedChatReadPermission),
429
+ apiProxy
430
+ );
431
+ router.delete(
432
+ "/v2/conversations/:conversation_id",
433
+ generalRateLimiter,
434
+ requirePermission(backstagePluginIntelligentAssistantCommon.lightspeedChatDeletePermission),
435
+ apiProxy
436
+ );
437
+ router.get(
438
+ "/v1/feedback/status",
439
+ generalRateLimiter,
440
+ requirePermission(backstagePluginIntelligentAssistantCommon.lightspeedChatReadPermission),
441
+ apiProxy
442
+ );
443
+ router.post(
444
+ "/v1/feedback",
445
+ generalRateLimiter,
446
+ requirePermission(backstagePluginIntelligentAssistantCommon.lightspeedChatCreatePermission),
447
+ async (request, response) => {
448
+ try {
449
+ const { userEntityRef } = getIdentity.getIdentity(request);
450
+ logger.info(`/v1/feedback receives call from user: ${userEntityRef}`);
451
+ const userQueryParam = `user_id=${encodeURIComponent(userEntityRef)}`;
452
+ const requestBody = JSON.stringify(request.body);
453
+ const fetchResponse = await fetch(
454
+ `${lcsBaseUrl}/v1/feedback?${userQueryParam}`,
455
+ {
456
+ method: "POST",
457
+ headers: {
458
+ "Content-Type": "application/json"
459
+ },
460
+ body: requestBody
461
+ }
462
+ );
463
+ if (!fetchResponse.ok) {
464
+ await utils.handleLCSFetchError(
465
+ fetchResponse,
466
+ logger,
467
+ "sending feedback",
468
+ response
469
+ );
470
+ return;
471
+ }
472
+ const data = await fetchResponse.json();
473
+ response.status(fetchResponse.status).json(data);
474
+ } catch (error) {
475
+ const errormsg = `Error while sending feedback: ${error}`;
476
+ logger.error(errormsg);
477
+ response.status(500).json({ error: errormsg });
478
+ }
479
+ }
480
+ );
481
+ router.post(
482
+ "/v1/query/interrupt",
483
+ generalRateLimiter,
484
+ requirePermission(backstagePluginIntelligentAssistantCommon.lightspeedChatCreatePermission),
485
+ async (request, response) => {
486
+ try {
487
+ const { userEntityRef } = getIdentity.getIdentity(request);
488
+ const userQueryParam = `user_id=${encodeURIComponent(userEntityRef)}`;
489
+ const requestBody = JSON.stringify(request.body);
490
+ const fetchResponse = await fetch(
491
+ `${lcsBaseUrl}/v1/streaming_query/interrupt?${userQueryParam}`,
492
+ {
493
+ method: "POST",
494
+ headers: {
495
+ "Content-Type": "application/json"
496
+ },
497
+ body: requestBody
498
+ }
499
+ );
500
+ if (!fetchResponse.ok) {
501
+ await utils.handleLCSFetchError(
502
+ fetchResponse,
503
+ logger,
504
+ "interrupting query",
505
+ response
506
+ );
507
+ return;
508
+ }
509
+ response.status(fetchResponse.status).json(await fetchResponse.json());
510
+ } catch (error) {
511
+ const errormsg = `Error while interrupting query: ${error}`;
512
+ logger.error(errormsg);
513
+ response.status(500).json({ error: errormsg });
514
+ }
515
+ }
516
+ );
517
+ router.post(
518
+ "/v1/query",
519
+ express__default.default.json({ limit: constant.EXPRESS_JSON_BODY_LIMIT }),
520
+ expensiveRateLimiter,
521
+ validation.validateCompletionsRequest,
522
+ requirePermission(backstagePluginIntelligentAssistantCommon.lightspeedChatCreatePermission),
523
+ async (request, response) => {
524
+ const { provider } = request.body;
525
+ try {
526
+ const { userEntityRef, credentials } = getIdentity.getIdentity(request);
527
+ logger.info(`/v1/query receives call from user: ${userEntityRef}`);
528
+ const userQueryParam = `user_id=${encodeURIComponent(userEntityRef)}`;
529
+ request.body.media_type = "application/json";
530
+ if (system_prompt && system_prompt.trim().length > 0) {
531
+ request.body.system_prompt = system_prompt;
532
+ }
533
+ const requestBody = JSON.stringify(request.body);
534
+ const mcpHeadersValue = await buildMcpHeaders(
535
+ staticServers,
536
+ settingsStore,
537
+ userEntityRef,
538
+ { dcrAuth: { authService: auth, credentials }, logger }
539
+ );
540
+ const abortController = new AbortController();
541
+ const fetchResponse = await fetch(
542
+ `${lcsBaseUrl}/v1/streaming_query?${userQueryParam}`,
543
+ {
544
+ method: "POST",
545
+ headers: {
546
+ "Content-Type": "application/json",
547
+ "MCP-HEADERS": mcpHeadersValue
548
+ },
549
+ body: requestBody,
550
+ signal: abortController.signal
551
+ }
552
+ );
553
+ if (!fetchResponse.ok) {
554
+ await utils.handleLCSFetchError(
555
+ fetchResponse,
556
+ logger,
557
+ "processing query",
558
+ response
559
+ );
560
+ return;
561
+ }
562
+ if (fetchResponse.body) {
563
+ const nodeStream = node_stream.Readable.fromWeb(fetchResponse.body);
564
+ nodeStream.on("error", (error) => {
565
+ logger.error(
566
+ `Upstream stream error while processing query: ${error}`
567
+ );
568
+ if (response.headersSent) {
569
+ response.destroy();
570
+ } else {
571
+ response.status(500).json({ error: "Stream error occurred" });
572
+ }
573
+ abortController.abort();
574
+ });
575
+ response.on("close", () => {
576
+ if (!response.writableFinished) {
577
+ logger.warn("Client disconnected while processing query");
578
+ nodeStream.destroy();
579
+ abortController.abort();
580
+ }
581
+ });
582
+ nodeStream.pipe(response);
583
+ }
584
+ } catch (error) {
585
+ const errormsg = `Error fetching completions from ${provider}: ${error}`;
586
+ logger.error(errormsg);
587
+ response.status(500).json({ error: errormsg });
588
+ }
589
+ }
590
+ );
591
+ router.put(
592
+ "/v2/conversations/:conversation_id",
593
+ generalRateLimiter,
594
+ requirePermission(backstagePluginIntelligentAssistantCommon.lightspeedChatCreatePermission),
595
+ async (request, response) => {
596
+ try {
597
+ const { userEntityRef } = getIdentity.getIdentity(request);
598
+ const conversation_id = request.params.conversation_id;
599
+ const requestBody = JSON.stringify(request.body);
600
+ const userQueryParam = `user_id=${encodeURIComponent(userEntityRef)}`;
601
+ const fetchResponse = await fetch(
602
+ `${lcsBaseUrl}/v2/conversations/${conversation_id}?${userQueryParam}`,
603
+ {
604
+ method: "PUT",
605
+ headers: {
606
+ "Content-Type": "application/json"
607
+ },
608
+ body: requestBody
609
+ }
610
+ );
611
+ if (!fetchResponse.ok) {
612
+ await utils.handleLCSFetchError(
613
+ fetchResponse,
614
+ logger,
615
+ "updating conversation",
616
+ response
617
+ );
618
+ return;
619
+ }
620
+ const data = await fetchResponse.json();
621
+ response.status(fetchResponse.status).json(data);
622
+ } catch (error) {
623
+ const errormsg = `Error while updating topic summary: ${error}`;
624
+ logger.error(errormsg);
625
+ response.status(500).json({ error: errormsg });
626
+ }
627
+ }
628
+ );
629
+ router.use((_request, response) => {
630
+ response.status(404).json({ error: "Requested path is not available" });
631
+ });
632
+ const middleware = rootHttpRouter.MiddlewareFactory.create({ logger, config });
633
+ router.use(middleware.error());
634
+ return router;
635
+ }
636
+
637
+ exports.createRouter = createRouter;
638
+ //# sourceMappingURL=router.cjs.js.map