@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.
- package/CHANGELOG.md +607 -0
- package/README.md +400 -0
- package/app-config.yaml +25 -0
- package/config.d.ts +142 -0
- package/dist/database/migration.cjs.js +19 -0
- package/dist/database/migration.cjs.js.map +1 -0
- package/dist/index.cjs.js +12 -0
- package/dist/index.cjs.js.map +1 -0
- package/dist/index.d.ts +73 -0
- package/dist/plugin.cjs.js +95 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/service/constant.cjs.js +107 -0
- package/dist/service/constant.cjs.js.map +1 -0
- package/dist/service/mcp-server-store.cjs.js +107 -0
- package/dist/service/mcp-server-store.cjs.js.map +1 -0
- package/dist/service/mcp-server-validator.cjs.js +215 -0
- package/dist/service/mcp-server-validator.cjs.js.map +1 -0
- package/dist/service/middleware/createPermissionMiddleware.cjs.js +24 -0
- package/dist/service/middleware/createPermissionMiddleware.cjs.js.map +1 -0
- package/dist/service/middleware/createRateLimitMiddleware.cjs.js +52 -0
- package/dist/service/middleware/createRateLimitMiddleware.cjs.js.map +1 -0
- package/dist/service/middleware/getIdentity.cjs.js +39 -0
- package/dist/service/middleware/getIdentity.cjs.js.map +1 -0
- package/dist/service/notebooks/VectorStoresOperator.cjs.js +347 -0
- package/dist/service/notebooks/VectorStoresOperator.cjs.js.map +1 -0
- package/dist/service/notebooks/documents/documentHelpers.cjs.js +36 -0
- package/dist/service/notebooks/documents/documentHelpers.cjs.js.map +1 -0
- package/dist/service/notebooks/documents/documentService.cjs.js +189 -0
- package/dist/service/notebooks/documents/documentService.cjs.js.map +1 -0
- package/dist/service/notebooks/documents/fileParser.cjs.js +139 -0
- package/dist/service/notebooks/documents/fileParser.cjs.js.map +1 -0
- package/dist/service/notebooks/notebooksRouters.cjs.js +477 -0
- package/dist/service/notebooks/notebooksRouters.cjs.js.map +1 -0
- package/dist/service/notebooks/sessions/sessionService.cjs.js +226 -0
- package/dist/service/notebooks/sessions/sessionService.cjs.js.map +1 -0
- package/dist/service/notebooks/types/notebooksTypes.cjs.js +40 -0
- package/dist/service/notebooks/types/notebooksTypes.cjs.js.map +1 -0
- package/dist/service/notebooks/utils.cjs.js +70 -0
- package/dist/service/notebooks/utils.cjs.js.map +1 -0
- package/dist/service/permission.cjs.js +26 -0
- package/dist/service/permission.cjs.js.map +1 -0
- package/dist/service/router.cjs.js +638 -0
- package/dist/service/router.cjs.js.map +1 -0
- package/dist/service/token-encryption.cjs.js +101 -0
- package/dist/service/token-encryption.cjs.js.map +1 -0
- package/dist/service/utils.cjs.js +40 -0
- package/dist/service/utils.cjs.js.map +1 -0
- package/dist/service/validation.cjs.js +45 -0
- package/dist/service/validation.cjs.js.map +1 -0
- package/migrations/20260302120000_add_mcp_servers.js +41 -0
- 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
|