@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 @@
1
+ {"version":3,"file":"createPermissionMiddleware.cjs.js","sources":["../../../src/service/middleware/createPermissionMiddleware.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { LoggerService } from '@backstage/backend-plugin-api';\nimport { NotAllowedError } from '@backstage/errors';\nimport type { BasicPermission } from '@backstage/plugin-permission-common';\n\nimport type { RequestHandler } from 'express';\n\nimport type { Authorizer } from '../types';\nimport { getIdentity } from './getIdentity';\n\nexport function createPermissionMiddleware(\n authorizer: Authorizer,\n permission: BasicPermission,\n logger: LoggerService,\n): RequestHandler {\n return async (req, res, next) => {\n try {\n const { credentials } = getIdentity(req);\n await authorizer.authorizeUser(permission, credentials);\n return next();\n } catch (error) {\n if (error instanceof NotAllowedError) {\n logger.error(error.message);\n return res.status(403).json({ error: error.message });\n }\n logger.error(`Unexpected authorization error: ${error}`);\n return res.status(500).json({ error: 'Internal authorization error' });\n }\n };\n}\n"],"names":["getIdentity","NotAllowedError"],"mappings":";;;;;AAyBO,SAAS,0BAAA,CACd,UAAA,EACA,UAAA,EACA,MAAA,EACgB;AAChB,EAAA,OAAO,OAAO,GAAA,EAAK,GAAA,EAAK,IAAA,KAAS;AAC/B,IAAA,IAAI;AACF,MAAA,MAAM,EAAE,WAAA,EAAY,GAAIA,uBAAA,CAAY,GAAG,CAAA;AACvC,MAAA,MAAM,UAAA,CAAW,aAAA,CAAc,UAAA,EAAY,WAAW,CAAA;AACtD,MAAA,OAAO,IAAA,EAAK;AAAA,IACd,SAAS,KAAA,EAAO;AACd,MAAA,IAAI,iBAAiBC,sBAAA,EAAiB;AACpC,QAAA,MAAA,CAAO,KAAA,CAAM,MAAM,OAAO,CAAA;AAC1B,QAAA,OAAO,GAAA,CAAI,OAAO,GAAG,CAAA,CAAE,KAAK,EAAE,KAAA,EAAO,KAAA,CAAM,OAAA,EAAS,CAAA;AAAA,MACtD;AACA,MAAA,MAAA,CAAO,KAAA,CAAM,CAAA,gCAAA,EAAmC,KAAK,CAAA,CAAE,CAAA;AACvD,MAAA,OAAO,GAAA,CAAI,OAAO,GAAG,CAAA,CAAE,KAAK,EAAE,KAAA,EAAO,gCAAgC,CAAA;AAAA,IACvE;AAAA,EACF,CAAA;AACF;;;;"}
@@ -0,0 +1,52 @@
1
+ 'use strict';
2
+
3
+ var rateLimit = require('express-rate-limit');
4
+ var constant = require('../constant.cjs.js');
5
+ var getIdentity = require('./getIdentity.cjs.js');
6
+
7
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
8
+
9
+ var rateLimit__default = /*#__PURE__*/_interopDefaultCompat(rateLimit);
10
+
11
+ function getRateLimitMax(config, tier) {
12
+ const configKey = tier === "expensive" ? "intelligent-assistant.rateLimit.expensive.max" : "intelligent-assistant.rateLimit.general.max";
13
+ const defaultMax = tier === "expensive" ? constant.DEFAULT_EXPENSIVE_RATE_LIMIT_MAX : constant.DEFAULT_GENERAL_RATE_LIMIT_MAX;
14
+ const configured = config.getOptionalNumber(configKey);
15
+ if (configured === void 0) {
16
+ return defaultMax;
17
+ }
18
+ if (configured <= 0) {
19
+ return 0;
20
+ }
21
+ return Math.floor(configured);
22
+ }
23
+ function createRateLimitMiddleware(config, tier, logger) {
24
+ const max = getRateLimitMax(config, tier);
25
+ if (max === 0) {
26
+ logger?.warn(`Rate limiting disabled for "${tier}" tier (max=0)`);
27
+ return (_req, _res, next) => next();
28
+ }
29
+ return rateLimit__default.default({
30
+ windowMs: constant.RATE_LIMIT_WINDOW_MS,
31
+ max,
32
+ standardHeaders: false,
33
+ legacyHeaders: false,
34
+ keyGenerator: (req) => getIdentity.getIdentity(req).userEntityRef,
35
+ handler: (req, res, _next, options) => {
36
+ const resetTime = req.rateLimit?.resetTime;
37
+ const retryAfter = resetTime ? Math.max(1, Math.ceil((resetTime.getTime() - Date.now()) / 1e3)) : Math.ceil(constant.RATE_LIMIT_WINDOW_MS / 1e3);
38
+ res.setHeader("Retry-After", String(retryAfter));
39
+ res.status(options.statusCode).json({
40
+ error: {
41
+ name: "RateLimitExceeded",
42
+ message: "Too many requests. Please try again later.",
43
+ retryAfter
44
+ }
45
+ });
46
+ }
47
+ });
48
+ }
49
+
50
+ exports.createRateLimitMiddleware = createRateLimitMiddleware;
51
+ exports.getRateLimitMax = getRateLimitMax;
52
+ //# sourceMappingURL=createRateLimitMiddleware.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createRateLimitMiddleware.cjs.js","sources":["../../../src/service/middleware/createRateLimitMiddleware.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { LoggerService } from '@backstage/backend-plugin-api';\nimport { Config } from '@backstage/config';\n\nimport type { RequestHandler } from 'express';\nimport rateLimit from 'express-rate-limit';\n\nimport {\n DEFAULT_EXPENSIVE_RATE_LIMIT_MAX,\n DEFAULT_GENERAL_RATE_LIMIT_MAX,\n RATE_LIMIT_WINDOW_MS,\n} from '../constant';\nimport { getIdentity } from './getIdentity';\n\nexport type RateLimitTier = 'expensive' | 'general';\n\nexport function getRateLimitMax(config: Config, tier: RateLimitTier): number {\n const configKey =\n tier === 'expensive'\n ? 'intelligent-assistant.rateLimit.expensive.max'\n : 'intelligent-assistant.rateLimit.general.max';\n const defaultMax =\n tier === 'expensive'\n ? DEFAULT_EXPENSIVE_RATE_LIMIT_MAX\n : DEFAULT_GENERAL_RATE_LIMIT_MAX;\n\n const configured = config.getOptionalNumber(configKey);\n if (configured === undefined) {\n return defaultMax;\n }\n\n if (configured <= 0) {\n return 0; // rate limit of 0 means disabled\n }\n\n return Math.floor(configured);\n}\n\nexport function createRateLimitMiddleware(\n config: Config,\n tier: RateLimitTier,\n logger?: LoggerService,\n): RequestHandler {\n const max = getRateLimitMax(config, tier);\n\n if (max === 0) {\n logger?.warn(`Rate limiting disabled for \"${tier}\" tier (max=0)`);\n return (_req, _res, next) => next();\n }\n\n return rateLimit({\n windowMs: RATE_LIMIT_WINDOW_MS,\n max,\n standardHeaders: false,\n legacyHeaders: false,\n keyGenerator: req => getIdentity(req).userEntityRef,\n handler: (req, res, _next, options) => {\n const resetTime = req.rateLimit?.resetTime;\n const retryAfter = resetTime\n ? Math.max(1, Math.ceil((resetTime.getTime() - Date.now()) / 1000))\n : Math.ceil(RATE_LIMIT_WINDOW_MS / 1000);\n\n res.setHeader('Retry-After', String(retryAfter));\n res.status(options.statusCode).json({\n error: {\n name: 'RateLimitExceeded',\n message: 'Too many requests. Please try again later.',\n retryAfter,\n },\n });\n },\n });\n}\n"],"names":["DEFAULT_EXPENSIVE_RATE_LIMIT_MAX","DEFAULT_GENERAL_RATE_LIMIT_MAX","rateLimit","RATE_LIMIT_WINDOW_MS","getIdentity"],"mappings":";;;;;;;;;;AA+BO,SAAS,eAAA,CAAgB,QAAgB,IAAA,EAA6B;AAC3E,EAAA,MAAM,SAAA,GACJ,IAAA,KAAS,WAAA,GACL,+CAAA,GACA,6CAAA;AACN,EAAA,MAAM,UAAA,GACJ,IAAA,KAAS,WAAA,GACLA,yCAAA,GACAC,uCAAA;AAEN,EAAA,MAAM,UAAA,GAAa,MAAA,CAAO,iBAAA,CAAkB,SAAS,CAAA;AACrD,EAAA,IAAI,eAAe,MAAA,EAAW;AAC5B,IAAA,OAAO,UAAA;AAAA,EACT;AAEA,EAAA,IAAI,cAAc,CAAA,EAAG;AACnB,IAAA,OAAO,CAAA;AAAA,EACT;AAEA,EAAA,OAAO,IAAA,CAAK,MAAM,UAAU,CAAA;AAC9B;AAEO,SAAS,yBAAA,CACd,MAAA,EACA,IAAA,EACA,MAAA,EACgB;AAChB,EAAA,MAAM,GAAA,GAAM,eAAA,CAAgB,MAAA,EAAQ,IAAI,CAAA;AAExC,EAAA,IAAI,QAAQ,CAAA,EAAG;AACb,IAAA,MAAA,EAAQ,IAAA,CAAK,CAAA,4BAAA,EAA+B,IAAI,CAAA,cAAA,CAAgB,CAAA;AAChE,IAAA,OAAO,CAAC,IAAA,EAAM,IAAA,EAAM,IAAA,KAAS,IAAA,EAAK;AAAA,EACpC;AAEA,EAAA,OAAOC,0BAAA,CAAU;AAAA,IACf,QAAA,EAAUC,6BAAA;AAAA,IACV,GAAA;AAAA,IACA,eAAA,EAAiB,KAAA;AAAA,IACjB,aAAA,EAAe,KAAA;AAAA,IACf,YAAA,EAAc,CAAA,GAAA,KAAOC,uBAAA,CAAY,GAAG,CAAA,CAAE,aAAA;AAAA,IACtC,OAAA,EAAS,CAAC,GAAA,EAAK,GAAA,EAAK,OAAO,OAAA,KAAY;AACrC,MAAA,MAAM,SAAA,GAAY,IAAI,SAAA,EAAW,SAAA;AACjC,MAAA,MAAM,aAAa,SAAA,GACf,IAAA,CAAK,IAAI,CAAA,EAAG,IAAA,CAAK,MAAM,SAAA,CAAU,OAAA,KAAY,IAAA,CAAK,GAAA,MAAS,GAAI,CAAC,IAChE,IAAA,CAAK,IAAA,CAAKD,gCAAuB,GAAI,CAAA;AAEzC,MAAA,GAAA,CAAI,SAAA,CAAU,aAAA,EAAe,MAAA,CAAO,UAAU,CAAC,CAAA;AAC/C,MAAA,GAAA,CAAI,MAAA,CAAO,OAAA,CAAQ,UAAU,CAAA,CAAE,IAAA,CAAK;AAAA,QAClC,KAAA,EAAO;AAAA,UACL,IAAA,EAAM,mBAAA;AAAA,UACN,OAAA,EAAS,4CAAA;AAAA,UACT;AAAA;AACF,OACD,CAAA;AAAA,IACH;AAAA,GACD,CAAA;AACH;;;;;"}
@@ -0,0 +1,39 @@
1
+ 'use strict';
2
+
3
+ var errors = require('@backstage/errors');
4
+
5
+ function createIdentityMiddleware(httpAuth, userInfo, logger) {
6
+ return async (req, res, next) => {
7
+ if (req.credentials && req.userEntityRef) {
8
+ return next();
9
+ }
10
+ try {
11
+ const credentials = await httpAuth.credentials(req);
12
+ const user = await userInfo.getUserInfo(credentials);
13
+ req.credentials = credentials;
14
+ req.userEntityRef = user.userEntityRef;
15
+ return next();
16
+ } catch (error) {
17
+ logger.error("Identity resolution failed for request", error);
18
+ if (error instanceof errors.NotAllowedError) {
19
+ return res.status(403).json({ error: "Forbidden" });
20
+ }
21
+ if (error instanceof errors.AuthenticationError) {
22
+ return res.status(401).json({ error: "Unauthorized" });
23
+ }
24
+ return res.status(500).json({ error: "Internal Server Error" });
25
+ }
26
+ };
27
+ }
28
+ function getIdentity(req) {
29
+ if (!req.credentials || !req.userEntityRef) {
30
+ throw new Error(
31
+ "Identity middleware did not run \u2014 credentials/userEntityRef missing on request"
32
+ );
33
+ }
34
+ return { credentials: req.credentials, userEntityRef: req.userEntityRef };
35
+ }
36
+
37
+ exports.createIdentityMiddleware = createIdentityMiddleware;
38
+ exports.getIdentity = getIdentity;
39
+ //# sourceMappingURL=getIdentity.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getIdentity.cjs.js","sources":["../../../src/service/middleware/getIdentity.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type {\n BackstageCredentials,\n HttpAuthService,\n LoggerService,\n UserInfoService,\n} from '@backstage/backend-plugin-api';\nimport { AuthenticationError, NotAllowedError } from '@backstage/errors';\n\nimport type { Request, RequestHandler } from 'express';\n\n/**\n * Creates an Express middleware that resolves Backstage identity once per\n * request and attaches `req.credentials` and `req.userEntityRef`.\n *\n * A guard check skips resolution if both fields are already populated,\n * making it safe for multiple routers to apply independently.\n */\nexport function createIdentityMiddleware(\n httpAuth: HttpAuthService,\n userInfo: UserInfoService,\n logger: LoggerService,\n): RequestHandler {\n return async (req, res, next) => {\n if (req.credentials && req.userEntityRef) {\n return next();\n }\n\n try {\n const credentials = await httpAuth.credentials(req);\n const user = await userInfo.getUserInfo(credentials);\n req.credentials = credentials;\n req.userEntityRef = user.userEntityRef;\n return next();\n } catch (error) {\n logger.error('Identity resolution failed for request', error);\n\n if (error instanceof NotAllowedError) {\n return res.status(403).json({ error: 'Forbidden' });\n }\n if (error instanceof AuthenticationError) {\n return res.status(401).json({ error: 'Unauthorized' });\n }\n return res.status(500).json({ error: 'Internal Server Error' });\n }\n };\n}\n\n/**\n * Extracts identity fields from the request, narrowing the types.\n * Throws if the identity middleware has not run.\n */\nexport function getIdentity(req: Request): {\n credentials: BackstageCredentials;\n userEntityRef: string;\n} {\n if (!req.credentials || !req.userEntityRef) {\n throw new Error(\n 'Identity middleware did not run — credentials/userEntityRef missing on request',\n );\n }\n return { credentials: req.credentials, userEntityRef: req.userEntityRef };\n}\n"],"names":["NotAllowedError","AuthenticationError"],"mappings":";;;;AAiCO,SAAS,wBAAA,CACd,QAAA,EACA,QAAA,EACA,MAAA,EACgB;AAChB,EAAA,OAAO,OAAO,GAAA,EAAK,GAAA,EAAK,IAAA,KAAS;AAC/B,IAAA,IAAI,GAAA,CAAI,WAAA,IAAe,GAAA,CAAI,aAAA,EAAe;AACxC,MAAA,OAAO,IAAA,EAAK;AAAA,IACd;AAEA,IAAA,IAAI;AACF,MAAA,MAAM,WAAA,GAAc,MAAM,QAAA,CAAS,WAAA,CAAY,GAAG,CAAA;AAClD,MAAA,MAAM,IAAA,GAAO,MAAM,QAAA,CAAS,WAAA,CAAY,WAAW,CAAA;AACnD,MAAA,GAAA,CAAI,WAAA,GAAc,WAAA;AAClB,MAAA,GAAA,CAAI,gBAAgB,IAAA,CAAK,aAAA;AACzB,MAAA,OAAO,IAAA,EAAK;AAAA,IACd,SAAS,KAAA,EAAO;AACd,MAAA,MAAA,CAAO,KAAA,CAAM,0CAA0C,KAAK,CAAA;AAE5D,MAAA,IAAI,iBAAiBA,sBAAA,EAAiB;AACpC,QAAA,OAAO,GAAA,CAAI,OAAO,GAAG,CAAA,CAAE,KAAK,EAAE,KAAA,EAAO,aAAa,CAAA;AAAA,MACpD;AACA,MAAA,IAAI,iBAAiBC,0BAAA,EAAqB;AACxC,QAAA,OAAO,GAAA,CAAI,OAAO,GAAG,CAAA,CAAE,KAAK,EAAE,KAAA,EAAO,gBAAgB,CAAA;AAAA,MACvD;AACA,MAAA,OAAO,GAAA,CAAI,OAAO,GAAG,CAAA,CAAE,KAAK,EAAE,KAAA,EAAO,yBAAyB,CAAA;AAAA,IAChE;AAAA,EACF,CAAA;AACF;AAMO,SAAS,YAAY,GAAA,EAG1B;AACA,EAAA,IAAI,CAAC,GAAA,CAAI,WAAA,IAAe,CAAC,IAAI,aAAA,EAAe;AAC1C,IAAA,MAAM,IAAI,KAAA;AAAA,MACR;AAAA,KACF;AAAA,EACF;AACA,EAAA,OAAO,EAAE,WAAA,EAAa,GAAA,CAAI,WAAA,EAAa,aAAA,EAAe,IAAI,aAAA,EAAc;AAC1E;;;;;"}
@@ -0,0 +1,347 @@
1
+ 'use strict';
2
+
3
+ var errors = require('@backstage/errors');
4
+ var FormData = require('form-data');
5
+
6
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
7
+
8
+ var FormData__default = /*#__PURE__*/_interopDefaultCompat(FormData);
9
+
10
+ function mapHttpStatusToError(status, defaultMessage, errorDetail) {
11
+ const detail = errorDetail?.detail;
12
+ const message = (typeof detail === "string" ? detail : detail?.message || detail?.cause) || defaultMessage;
13
+ switch (status) {
14
+ case 404:
15
+ return new errors.NotFoundError(message);
16
+ case 409:
17
+ return new errors.ConflictError(message);
18
+ case 400:
19
+ return new errors.InputError(message);
20
+ case 403:
21
+ return new errors.NotAllowedError(message);
22
+ default:
23
+ return new Error(message);
24
+ }
25
+ }
26
+ async function handleHttpError(response, logger, operation) {
27
+ let error;
28
+ try {
29
+ error = await response.json();
30
+ } catch {
31
+ error = { detail: await response.text() };
32
+ }
33
+ logger.error(`Failed to ${operation}:`, error);
34
+ let status = response.status;
35
+ if (status === 500 && operation === "retrieve vector store") {
36
+ logger.warn(
37
+ `Treating 500 error as 404 for ${operation} (lightspeed-core limitation)`
38
+ );
39
+ status = 404;
40
+ }
41
+ throw mapHttpStatusToError(status, `Failed to ${operation}`, error);
42
+ }
43
+ class VectorStoresOperator {
44
+ static instance = null;
45
+ baseURL;
46
+ logger;
47
+ constructor(lightspeedCoreUrl, logger) {
48
+ this.baseURL = lightspeedCoreUrl;
49
+ this.logger = logger;
50
+ }
51
+ /**
52
+ * Get the singleton instance of VectorStoresOperator
53
+ * @param lightspeedCoreUrl - Lightspeed core URL (required on first call)
54
+ * @param logger - Logger service (required on first call)
55
+ * @returns The singleton instance
56
+ */
57
+ static getInstance(lightspeedCoreUrl, logger) {
58
+ if (!VectorStoresOperator.instance) {
59
+ VectorStoresOperator.instance = new VectorStoresOperator(
60
+ lightspeedCoreUrl,
61
+ logger
62
+ );
63
+ }
64
+ return VectorStoresOperator.instance;
65
+ }
66
+ /**
67
+ * Reset the singleton instance (primarily for testing)
68
+ */
69
+ static resetInstance() {
70
+ VectorStoresOperator.instance = null;
71
+ }
72
+ /**
73
+ * Vector Stores API - mirrors LlamaStackClient.vectorStores structure
74
+ */
75
+ vectorStores = {
76
+ /**
77
+ * Create a new vector store
78
+ * POST /v1/vector-stores
79
+ */
80
+ create: async (params) => {
81
+ this.logger.debug("VectorStoresOperator: Creating vector store", params);
82
+ const response = await fetch(`${this.baseURL}/v1/vector-stores`, {
83
+ method: "POST",
84
+ headers: {
85
+ "Content-Type": "application/json"
86
+ },
87
+ body: JSON.stringify(params)
88
+ });
89
+ if (!response.ok) {
90
+ await handleHttpError(response, this.logger, "create vector store");
91
+ }
92
+ return response.json();
93
+ },
94
+ /**
95
+ * Retrieve a vector store by ID
96
+ * GET /v1/vector-stores/{id}
97
+ */
98
+ retrieve: async (vectorStoreId) => {
99
+ this.logger.debug(
100
+ `VectorStoresOperator: Retrieving vector store ${vectorStoreId}`
101
+ );
102
+ const response = await fetch(
103
+ `${this.baseURL}/v1/vector-stores/${vectorStoreId}`,
104
+ {
105
+ method: "GET",
106
+ headers: {
107
+ "Content-Type": "application/json"
108
+ }
109
+ }
110
+ );
111
+ if (!response.ok) {
112
+ await handleHttpError(response, this.logger, "retrieve vector store");
113
+ }
114
+ return response.json();
115
+ },
116
+ /**
117
+ * Update a vector store
118
+ * PUT /v1/vector-stores/{id}
119
+ */
120
+ update: async (vectorStoreId, params) => {
121
+ this.logger.debug(
122
+ `VectorStoresOperator: Updating vector store ${vectorStoreId}`,
123
+ params
124
+ );
125
+ const response = await fetch(
126
+ `${this.baseURL}/v1/vector-stores/${vectorStoreId}`,
127
+ {
128
+ method: "PUT",
129
+ headers: {
130
+ "Content-Type": "application/json"
131
+ },
132
+ body: JSON.stringify(params)
133
+ }
134
+ );
135
+ if (!response.ok) {
136
+ await handleHttpError(response, this.logger, "update vector store");
137
+ }
138
+ return response.json();
139
+ },
140
+ /**
141
+ * Delete a vector store
142
+ * DELETE /v1/vector-stores/{id}
143
+ */
144
+ delete: async (vectorStoreId) => {
145
+ this.logger.debug(
146
+ `VectorStoresOperator: Deleting vector store ${vectorStoreId}`
147
+ );
148
+ const response = await fetch(
149
+ `${this.baseURL}/v1/vector-stores/${vectorStoreId}`,
150
+ {
151
+ method: "DELETE",
152
+ headers: {
153
+ "Content-Type": "application/json"
154
+ }
155
+ }
156
+ );
157
+ if (!response.ok) {
158
+ await handleHttpError(response, this.logger, "delete vector store");
159
+ }
160
+ if (response.status === 204 || response.headers.get("content-length") === "0") {
161
+ return { deleted: true };
162
+ }
163
+ return response.json();
164
+ },
165
+ /**
166
+ * List all vector stores
167
+ * GET /v1/vector-stores
168
+ */
169
+ list: async () => {
170
+ this.logger.debug("VectorStoresOperator: Listing vector stores");
171
+ const response = await fetch(`${this.baseURL}/v1/vector-stores`, {
172
+ method: "GET",
173
+ headers: {
174
+ "Content-Type": "application/json"
175
+ }
176
+ });
177
+ if (!response.ok) {
178
+ await handleHttpError(response, this.logger, "list vector stores");
179
+ }
180
+ return response.json();
181
+ },
182
+ /**
183
+ * Vector Store Files API - nested structure matching LlamaStackClient
184
+ */
185
+ files: {
186
+ /**
187
+ * Add a file to a vector store
188
+ * POST /v1/vector-stores/{id}/files
189
+ */
190
+ create: async (vectorStoreId, params) => {
191
+ this.logger.debug(
192
+ `VectorStoresOperator: Adding file to vector store ${vectorStoreId}`,
193
+ params
194
+ );
195
+ const response = await fetch(
196
+ `${this.baseURL}/v1/vector-stores/${vectorStoreId}/files`,
197
+ {
198
+ method: "POST",
199
+ headers: {
200
+ "Content-Type": "application/json"
201
+ },
202
+ body: JSON.stringify(params)
203
+ }
204
+ );
205
+ if (!response.ok) {
206
+ await handleHttpError(
207
+ response,
208
+ this.logger,
209
+ "add file to vector store"
210
+ );
211
+ }
212
+ return response.json();
213
+ },
214
+ /**
215
+ * List files in a vector store
216
+ * GET /v1/vector-stores/{id}/files
217
+ */
218
+ list: async (vectorStoreId) => {
219
+ this.logger.debug(
220
+ `VectorStoresOperator: Listing files in vector store ${vectorStoreId}`
221
+ );
222
+ const response = await fetch(
223
+ `${this.baseURL}/v1/vector-stores/${vectorStoreId}/files`,
224
+ {
225
+ method: "GET",
226
+ headers: {
227
+ "Content-Type": "application/json"
228
+ }
229
+ }
230
+ );
231
+ if (!response.ok) {
232
+ await handleHttpError(response, this.logger, "list files");
233
+ }
234
+ return response.json();
235
+ },
236
+ /**
237
+ * Retrieve a file from a vector store
238
+ * GET /v1/vector-stores/{id}/files/{file_id}
239
+ */
240
+ retrieve: async (vectorStoreId, fileId) => {
241
+ this.logger.debug(
242
+ `VectorStoresOperator: Retrieving file ${fileId} from vector store ${vectorStoreId}`
243
+ );
244
+ const response = await fetch(
245
+ `${this.baseURL}/v1/vector-stores/${vectorStoreId}/files/${fileId}`,
246
+ {
247
+ method: "GET",
248
+ headers: {
249
+ "Content-Type": "application/json"
250
+ }
251
+ }
252
+ );
253
+ if (!response.ok) {
254
+ await handleHttpError(response, this.logger, "retrieve file");
255
+ }
256
+ return response.json();
257
+ },
258
+ /**
259
+ * Delete a file from a vector store
260
+ * DELETE /v1/vector-stores/{id}/files/{file_id}
261
+ */
262
+ delete: async (vectorStoreId, fileId) => {
263
+ this.logger.debug(
264
+ `VectorStoresOperator: Deleting file ${fileId} from vector store ${vectorStoreId}`
265
+ );
266
+ const response = await fetch(
267
+ `${this.baseURL}/v1/vector-stores/${vectorStoreId}/files/${fileId}`,
268
+ {
269
+ method: "DELETE",
270
+ headers: {
271
+ "Content-Type": "application/json"
272
+ }
273
+ }
274
+ );
275
+ if (!response.ok) {
276
+ await handleHttpError(response, this.logger, "delete file");
277
+ }
278
+ if (response.status === 204 || response.headers.get("content-length") === "0") {
279
+ return { deleted: true };
280
+ }
281
+ return response.json();
282
+ }
283
+ }
284
+ };
285
+ /**
286
+ * Files API - mirrors LlamaStackClient.files structure
287
+ */
288
+ files = {
289
+ /**
290
+ * Upload a file
291
+ * POST /v1/files
292
+ */
293
+ create: async (params) => {
294
+ this.logger.debug("VectorStoresOperator: Uploading file", {
295
+ purpose: params.purpose,
296
+ fileName: params.file.name
297
+ });
298
+ const formData = new FormData__default.default();
299
+ formData.append("file", params.file.buffer, {
300
+ filename: params.file.name,
301
+ contentType: params.file.type || "text/plain"
302
+ });
303
+ formData.append("purpose", params.purpose);
304
+ const formBuffer = await new Promise((resolve, reject) => {
305
+ const chunks = [];
306
+ formData.on("data", (chunk) => {
307
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
308
+ });
309
+ formData.on("end", () => resolve(Buffer.concat(chunks)));
310
+ formData.on("error", reject);
311
+ formData.resume();
312
+ });
313
+ const response = await fetch(`${this.baseURL}/v1/files`, {
314
+ method: "POST",
315
+ body: formBuffer,
316
+ headers: formData.getHeaders()
317
+ });
318
+ if (!response.ok) {
319
+ await handleHttpError(response, this.logger, "upload file");
320
+ }
321
+ return response.json();
322
+ },
323
+ /**
324
+ * Delete a file
325
+ * DELETE /v1/files/{file_id}
326
+ */
327
+ delete: async (fileId) => {
328
+ this.logger.debug(`VectorStoresOperator: Deleting file ${fileId}`);
329
+ const response = await fetch(`${this.baseURL}/v1/files/${fileId}`, {
330
+ method: "DELETE",
331
+ headers: {
332
+ "Content-Type": "application/json"
333
+ }
334
+ });
335
+ if (!response.ok) {
336
+ await handleHttpError(response, this.logger, "delete file");
337
+ }
338
+ if (response.status === 204 || response.headers.get("content-length") === "0") {
339
+ return { deleted: true };
340
+ }
341
+ return response.json();
342
+ }
343
+ };
344
+ }
345
+
346
+ exports.VectorStoresOperator = VectorStoresOperator;
347
+ //# sourceMappingURL=VectorStoresOperator.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VectorStoresOperator.cjs.js","sources":["../../../src/service/notebooks/VectorStoresOperator.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { LoggerService } from '@backstage/backend-plugin-api';\nimport {\n ConflictError,\n InputError,\n NotAllowedError,\n NotFoundError,\n} from '@backstage/errors';\n\nimport FormData from 'form-data';\n\n/**\n * Map HTTP status code to appropriate Backstage error type\n * @param status - HTTP status code\n * @param defaultMessage - Default error message\n * @param errorDetail - Error details from response\n * @returns Appropriate Backstage error instance\n */\nfunction mapHttpStatusToError(\n status: number,\n defaultMessage: string,\n errorDetail?: any,\n): Error {\n const detail = errorDetail?.detail;\n const message =\n (typeof detail === 'string' ? detail : detail?.message || detail?.cause) ||\n defaultMessage;\n\n switch (status) {\n case 404:\n return new NotFoundError(message);\n case 409:\n return new ConflictError(message);\n case 400:\n return new InputError(message);\n case 403:\n return new NotAllowedError(message);\n default:\n return new Error(message);\n }\n}\n\n/**\n * Handle HTTP error response\n * @param response - Fetch response object\n * @param logger - Logger service\n * @param operation - Operation description for logging\n * @returns Never (always throws)\n */\nasync function handleHttpError(\n response: Response,\n logger: LoggerService,\n operation: string,\n): Promise<never> {\n let error;\n try {\n error = await response.json();\n } catch {\n error = { detail: await response.text() };\n }\n logger.error(`Failed to ${operation}:`, error);\n\n let status = response.status;\n if (status === 500 && operation === 'retrieve vector store') {\n logger.warn(\n `Treating 500 error as 404 for ${operation} (lightspeed-core limitation)`,\n );\n status = 404;\n }\n\n throw mapHttpStatusToError(status, `Failed to ${operation}`, error);\n}\n\n/**\n * VectorStoresOperator - HTTP client wrapper for lightspeed-core vector store endpoints\n *\n * This class provides the same interface as LlamaStackClient but proxies calls through\n * lightspeed-core REST API instead of calling llama stack directly.\n *\n * Implemented as a singleton to ensure single instance across the application.\n */\nexport class VectorStoresOperator {\n private static instance: VectorStoresOperator | null = null;\n private baseURL: string;\n private logger: LoggerService;\n\n private constructor(lightspeedCoreUrl: string, logger: LoggerService) {\n this.baseURL = lightspeedCoreUrl;\n this.logger = logger;\n }\n\n /**\n * Get the singleton instance of VectorStoresOperator\n * @param lightspeedCoreUrl - Lightspeed core URL (required on first call)\n * @param logger - Logger service (required on first call)\n * @returns The singleton instance\n */\n static getInstance(\n lightspeedCoreUrl: string,\n logger: LoggerService,\n ): VectorStoresOperator {\n if (!VectorStoresOperator.instance) {\n VectorStoresOperator.instance = new VectorStoresOperator(\n lightspeedCoreUrl,\n logger,\n );\n }\n return VectorStoresOperator.instance;\n }\n\n /**\n * Reset the singleton instance (primarily for testing)\n */\n static resetInstance(): void {\n VectorStoresOperator.instance = null;\n }\n\n /**\n * Vector Stores API - mirrors LlamaStackClient.vectorStores structure\n */\n vectorStores = {\n /**\n * Create a new vector store\n * POST /v1/vector-stores\n */\n create: async (params: {\n name: string;\n provider_id?: string;\n embedding_model?: string;\n embedding_dimension?: number;\n metadata?: Record<string, any>;\n }): Promise<any> => {\n this.logger.debug('VectorStoresOperator: Creating vector store', params);\n\n const response = await fetch(`${this.baseURL}/v1/vector-stores`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(params),\n });\n\n if (!response.ok) {\n await handleHttpError(response, this.logger, 'create vector store');\n }\n\n return response.json();\n },\n\n /**\n * Retrieve a vector store by ID\n * GET /v1/vector-stores/{id}\n */\n retrieve: async (vectorStoreId: string): Promise<any> => {\n this.logger.debug(\n `VectorStoresOperator: Retrieving vector store ${vectorStoreId}`,\n );\n\n const response = await fetch(\n `${this.baseURL}/v1/vector-stores/${vectorStoreId}`,\n {\n method: 'GET',\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n );\n if (!response.ok) {\n await handleHttpError(response, this.logger, 'retrieve vector store');\n }\n\n return response.json();\n },\n\n /**\n * Update a vector store\n * PUT /v1/vector-stores/{id}\n */\n update: async (\n vectorStoreId: string,\n params: {\n embedding_model?: string;\n embedding_dimension?: number;\n metadata?: Record<string, any>;\n },\n ): Promise<any> => {\n this.logger.debug(\n `VectorStoresOperator: Updating vector store ${vectorStoreId}`,\n params,\n );\n\n const response = await fetch(\n `${this.baseURL}/v1/vector-stores/${vectorStoreId}`,\n {\n method: 'PUT',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(params),\n },\n );\n\n if (!response.ok) {\n await handleHttpError(response, this.logger, 'update vector store');\n }\n\n return response.json();\n },\n\n /**\n * Delete a vector store\n * DELETE /v1/vector-stores/{id}\n */\n delete: async (vectorStoreId: string): Promise<any> => {\n this.logger.debug(\n `VectorStoresOperator: Deleting vector store ${vectorStoreId}`,\n );\n\n const response = await fetch(\n `${this.baseURL}/v1/vector-stores/${vectorStoreId}`,\n {\n method: 'DELETE',\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n );\n\n if (!response.ok) {\n await handleHttpError(response, this.logger, 'delete vector store');\n }\n\n // DELETE may return 204 No Content or empty body\n if (\n response.status === 204 ||\n response.headers.get('content-length') === '0'\n ) {\n return { deleted: true };\n }\n\n return response.json();\n },\n\n /**\n * List all vector stores\n * GET /v1/vector-stores\n */\n list: async (): Promise<any> => {\n this.logger.debug('VectorStoresOperator: Listing vector stores');\n\n const response = await fetch(`${this.baseURL}/v1/vector-stores`, {\n method: 'GET',\n headers: {\n 'Content-Type': 'application/json',\n },\n });\n\n if (!response.ok) {\n await handleHttpError(response, this.logger, 'list vector stores');\n }\n\n return response.json();\n },\n\n /**\n * Vector Store Files API - nested structure matching LlamaStackClient\n */\n files: {\n /**\n * Add a file to a vector store\n * POST /v1/vector-stores/{id}/files\n */\n create: async (\n vectorStoreId: string,\n params: {\n file_id: string;\n attributes?: Record<string, any>;\n chunking_strategy?: any;\n },\n ): Promise<any> => {\n this.logger.debug(\n `VectorStoresOperator: Adding file to vector store ${vectorStoreId}`,\n params,\n );\n\n const response = await fetch(\n `${this.baseURL}/v1/vector-stores/${vectorStoreId}/files`,\n {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(params),\n },\n );\n\n if (!response.ok) {\n await handleHttpError(\n response,\n this.logger,\n 'add file to vector store',\n );\n }\n\n return response.json();\n },\n\n /**\n * List files in a vector store\n * GET /v1/vector-stores/{id}/files\n */\n list: async (vectorStoreId: string): Promise<any> => {\n this.logger.debug(\n `VectorStoresOperator: Listing files in vector store ${vectorStoreId}`,\n );\n\n const response = await fetch(\n `${this.baseURL}/v1/vector-stores/${vectorStoreId}/files`,\n {\n method: 'GET',\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n );\n\n if (!response.ok) {\n await handleHttpError(response, this.logger, 'list files');\n }\n\n return response.json();\n },\n\n /**\n * Retrieve a file from a vector store\n * GET /v1/vector-stores/{id}/files/{file_id}\n */\n retrieve: async (vectorStoreId: string, fileId: string): Promise<any> => {\n this.logger.debug(\n `VectorStoresOperator: Retrieving file ${fileId} from vector store ${vectorStoreId}`,\n );\n\n const response = await fetch(\n `${this.baseURL}/v1/vector-stores/${vectorStoreId}/files/${fileId}`,\n {\n method: 'GET',\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n );\n\n if (!response.ok) {\n await handleHttpError(response, this.logger, 'retrieve file');\n }\n\n return response.json();\n },\n\n /**\n * Delete a file from a vector store\n * DELETE /v1/vector-stores/{id}/files/{file_id}\n */\n delete: async (vectorStoreId: string, fileId: string): Promise<any> => {\n this.logger.debug(\n `VectorStoresOperator: Deleting file ${fileId} from vector store ${vectorStoreId}`,\n );\n\n const response = await fetch(\n `${this.baseURL}/v1/vector-stores/${vectorStoreId}/files/${fileId}`,\n {\n method: 'DELETE',\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n );\n\n if (!response.ok) {\n await handleHttpError(response, this.logger, 'delete file');\n }\n\n // DELETE may return 204 No Content or empty body\n if (\n response.status === 204 ||\n response.headers.get('content-length') === '0'\n ) {\n return { deleted: true };\n }\n\n return response.json();\n },\n },\n };\n\n /**\n * Files API - mirrors LlamaStackClient.files structure\n */\n files = {\n /**\n * Upload a file\n * POST /v1/files\n */\n create: async (params: {\n file: any; // File-like object from toFile()\n purpose: string;\n }): Promise<any> => {\n this.logger.debug('VectorStoresOperator: Uploading file', {\n purpose: params.purpose,\n fileName: params.file.name,\n });\n\n // Create FormData for multipart upload using form-data package\n const formData = new FormData();\n\n // Append buffer directly with metadata\n formData.append('file', params.file.buffer, {\n filename: params.file.name,\n contentType: params.file.type || 'text/plain',\n });\n formData.append('purpose', params.purpose);\n\n // Convert FormData stream to Buffer\n const formBuffer = await new Promise<Buffer>((resolve, reject) => {\n const chunks: Buffer[] = [];\n formData.on('data', (chunk: string | Buffer) => {\n chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));\n });\n formData.on('end', () => resolve(Buffer.concat(chunks)));\n formData.on('error', reject);\n formData.resume();\n });\n\n const response = await fetch(`${this.baseURL}/v1/files`, {\n method: 'POST',\n body: formBuffer as unknown as BodyInit,\n headers: formData.getHeaders(),\n });\n\n if (!response.ok) {\n await handleHttpError(response, this.logger, 'upload file');\n }\n\n return response.json();\n },\n\n /**\n * Delete a file\n * DELETE /v1/files/{file_id}\n */\n delete: async (fileId: string): Promise<any> => {\n this.logger.debug(`VectorStoresOperator: Deleting file ${fileId}`);\n\n const response = await fetch(`${this.baseURL}/v1/files/${fileId}`, {\n method: 'DELETE',\n headers: {\n 'Content-Type': 'application/json',\n },\n });\n\n if (!response.ok) {\n await handleHttpError(response, this.logger, 'delete file');\n }\n\n if (\n response.status === 204 ||\n response.headers.get('content-length') === '0'\n ) {\n return { deleted: true };\n }\n\n return response.json();\n },\n };\n}\n"],"names":["NotFoundError","ConflictError","InputError","NotAllowedError","FormData"],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ 'use strict';
2
+
3
+ var errors = require('@backstage/errors');
4
+ require('htmlparser2');
5
+ require('dns/promises');
6
+ require('net');
7
+ var constant = require('../../constant.cjs.js');
8
+ var fileParser = require('./fileParser.cjs.js');
9
+
10
+ const isValidFileSize = (fileSize) => {
11
+ const maxSize = constant.DEFAULT_MAX_FILE_SIZE_MB;
12
+ return fileSize <= maxSize;
13
+ };
14
+ const isValidFileType = (fileType) => {
15
+ const normalizedType = fileType.toLowerCase().replace(/^\./, "");
16
+ return Object.values(constant.SupportedFileType).includes(
17
+ normalizedType
18
+ );
19
+ };
20
+ const parseFileContent = async (logger, fileType, file) => {
21
+ if (!file) {
22
+ throw new errors.InputError("No file uploaded");
23
+ }
24
+ if (!isValidFileSize(file.size)) {
25
+ throw new errors.InputError(
26
+ `File size exceeds ${constant.DEFAULT_MAX_FILE_SIZE_MB / 1024 / 1024}MB limit`
27
+ );
28
+ }
29
+ logger.info(`Parsing file ${file.originalname} for fileType ${fileType}`);
30
+ return await fileParser.parseFile(file.buffer, file.originalname, fileType);
31
+ };
32
+
33
+ exports.isValidFileSize = isValidFileSize;
34
+ exports.isValidFileType = isValidFileType;
35
+ exports.parseFileContent = parseFileContent;
36
+ //# sourceMappingURL=documentHelpers.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"documentHelpers.cjs.js","sources":["../../../../src/service/notebooks/documents/documentHelpers.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { LoggerService } from '@backstage/backend-plugin-api';\nimport { InputError } from '@backstage/errors';\n\nimport { Parser } from 'htmlparser2';\n\nimport dns from 'dns/promises';\nimport { isIP } from 'net';\n\nimport {\n DEFAULT_MAX_FILE_SIZE_MB,\n FILTERED_CONTENT_MARKER,\n HTML_BLOCK_TAGS,\n HTML_IGNORED_TAGS,\n MAX_CONSECUTIVE_NEWLINES,\n PROMPT_INJECTION_PATTERNS,\n SSRF_BLOCKED_HOSTNAMES,\n SupportedFileType,\n} from '../../constant';\nimport { parseFile } from './fileParser';\n\n// ==============================================================================\n// URL Support Functions (Reserved for Future URL File Type Support)\n// ==============================================================================\n// The following functions are currently unused but reserved for future\n// implementation of URL file type parsing. They provide:\n// - URL validation and format checking\n// - SSRF (Server-Side Request Forgery) protection\n// - HTML content parsing and sanitization\n// - Prompt injection attack prevention\n//\n// These functions were part of the URL parsing implementation that was removed\n// but are kept here to facilitate re-implementation if needed in the future.\n// ==============================================================================\n\n/**\n * Validate URL format\n * @reserved Reserved for future URL file type support\n */\nexport const isValidURL = (urlString: string): boolean => {\n try {\n const url = new URL(urlString);\n return url.protocol === 'http:' || url.protocol === 'https:';\n } catch {\n return false;\n }\n};\n\n/**\n * Validate file size (max 20MB by default)\n */\nexport const isValidFileSize = (fileSize: number): boolean => {\n const maxSize = DEFAULT_MAX_FILE_SIZE_MB;\n return fileSize <= maxSize;\n};\n\n/**\n * Validate file type\n */\nexport const isValidFileType = (fileType: string): boolean => {\n const normalizedType = fileType.toLowerCase().replace(/^\\./, '');\n return Object.values(SupportedFileType).includes(\n normalizedType as SupportedFileType,\n );\n};\n\n/**\n * Parse file from upload\n * @param logger - Logger service\n * @param fileType - File type\n * @param file - File to parse\n * @returns Parsed file\n */\nexport const parseFileContent = async (\n logger: LoggerService,\n fileType: string,\n file?: Express.Multer.File,\n) => {\n if (!file) {\n throw new InputError('No file uploaded');\n }\n if (!isValidFileSize(file.size)) {\n throw new InputError(\n `File size exceeds ${DEFAULT_MAX_FILE_SIZE_MB / 1024 / 1024}MB limit`,\n );\n }\n logger.info(`Parsing file ${file.originalname} for fileType ${fileType}`);\n return await parseFile(file.buffer, file.originalname, fileType);\n};\n\n/**\n * Check if an IP address is private, internal, or a metadata endpoint\n * Blocks SSRF attacks to internal networks\n * @reserved Reserved for future URL file type support\n */\nconst isPrivateOrInternalIP = (ip: string): boolean => {\n // Check if it's a valid IP\n const ipVersion = isIP(ip);\n if (ipVersion === 0) {\n return false; // Not a valid IP\n }\n\n if (ipVersion === 4) {\n const parts = ip.split('.').map(Number);\n\n switch (parts[0]) {\n case 0: // 0.0.0.0/8 - Current network\n case 10: // 10.0.0.0/8 - Private\n case 127: // 127.0.0.0/8 - Loopback\n return true;\n case 100: // 100.64.0.0/10 - Carrier-grade NAT\n return parts[1] >= 64 && parts[1] <= 127;\n case 169: // 169.254.0.0/16 - Link-local (includes cloud metadata endpoint 169.254.169.254)\n return parts[1] === 254;\n case 172: // 172.16.0.0/12 - Private\n return parts[1] >= 16 && parts[1] <= 31;\n case 192: // 192.168.0.0/16 - Private\n return parts[1] === 168;\n default:\n // 224.0.0.0/4 - Multicast, 240.0.0.0/4 - Reserved\n return parts[0] >= 224;\n }\n } else if (ipVersion === 6) {\n const lower = ip.toLowerCase();\n\n // ::1 - Loopback\n if (lower === '::1' || lower === '0:0:0:0:0:0:0:1') return true;\n\n // fc00::/7 - Unique local address\n if (lower.startsWith('fc') || lower.startsWith('fd')) return true;\n\n // fe80::/10 - Link-local\n if (\n lower.startsWith('fe8') ||\n lower.startsWith('fe9') ||\n lower.startsWith('fea') ||\n lower.startsWith('feb')\n )\n return true;\n\n // ::ffff:0:0/96 - IPv4-mapped IPv6\n if (lower.startsWith('::ffff:')) {\n const ipv4Part = ip.substring(7);\n // Check if it's in dotted-decimal format (e.g., ::ffff:127.0.0.1)\n if (ipv4Part.includes('.')) {\n return isPrivateOrInternalIP(ipv4Part);\n }\n // Handle hex format (e.g., ::ffff:7f00:1 which is 127.0.0.1)\n // Extract the hex parts and check against private ranges\n const parts = ipv4Part.split(':');\n if (parts.length >= 1) {\n const firstHex = parseInt(parts[0], 16);\n // Check for common private ranges in hex:\n // 127.x.x.x -> 0x7F00-0x7FFF (loopback)\n // 10.x.x.x -> 0x0A00-0x0AFF (private)\n // 192.168.x.x -> 0xC0A8 (private)\n // 172.16-31.x.x -> 0xAC10-0xAC1F (private)\n // 169.254.x.x -> 0xA9FE (link-local)\n if (\n (firstHex >= 0x7f00 && firstHex <= 0x7fff) || // 127.x.x.x\n (firstHex >= 0x0a00 && firstHex <= 0x0aff) || // 10.x.x.x\n firstHex === 0xc0a8 || // 192.168.x.x\n (firstHex >= 0xac10 && firstHex <= 0xac1f) || // 172.16-31.x.x\n firstHex === 0xa9fe // 169.254.x.x\n ) {\n return true;\n }\n }\n }\n }\n\n return false;\n};\n\n/**\n * Validate URL and check for SSRF (Server-Side Request Forgery) vulnerabilities\n *\n * This function protects against SSRF attacks by:\n * 1. Blocking access to private/internal IP ranges (RFC 1918, link-local, loopback)\n * 2. Blocking access to cloud metadata endpoints (AWS, GCP, Azure)\n * 3. Resolving hostnames to IPs to prevent DNS rebinding attacks\n *\n * @param urlString - URL to validate\n * @throws InputError if URL points to blocked hostname or private IP\n * @reserved Reserved for future URL file type support\n */\nexport const validateURLForSSRF = async (urlString: string): Promise<void> => {\n const url = new URL(urlString);\n\n // Strip brackets from IPv6 addresses (e.g., [::1] -> ::1)\n const hostname = url.hostname.replace(/^\\[|\\]$/g, '');\n\n // Check if hostname is already an IP address\n const ipVersion = isIP(hostname);\n if (ipVersion !== 0) {\n if (isPrivateOrInternalIP(hostname)) {\n throw new InputError(\n 'Access to private/internal IP addresses is not allowed',\n );\n }\n return;\n }\n\n // Block localhost and common internal hostnames used in SSRF attacks\n const lowerHostname = hostname.toLowerCase();\n if (SSRF_BLOCKED_HOSTNAMES.includes(lowerHostname as any)) {\n throw new InputError(`Access to ${lowerHostname} is not allowed`);\n }\n\n // Resolve hostname to IP addresses\n try {\n const addresses = await dns.resolve(hostname);\n\n // Check all resolved IPs\n for (const address of addresses) {\n if (isPrivateOrInternalIP(address)) {\n throw new InputError(\n `URL resolves to private/internal IP address (${address}), which is not allowed`,\n );\n }\n }\n } catch (error: any) {\n // Re-throw InputError from isPrivateOrInternalIP check\n if (error instanceof InputError) {\n throw error;\n }\n // DNS resolution failure is a user input issue (invalid hostname)\n throw new InputError(`Failed to resolve hostname: ${error.message}`);\n }\n};\n\n/**\n * Strip HTML tags and extract readable text from HTML content\n * @reserved Reserved for future URL file type support\n */\nexport const stripHtmlTags = (html: string): string => {\n let text = '';\n let ignoring = false;\n\n const parser = new Parser(\n {\n onopentag(name) {\n if (HTML_IGNORED_TAGS.has(name)) ignoring = true;\n },\n onclosetag(name) {\n if (HTML_IGNORED_TAGS.has(name)) ignoring = false;\n if (HTML_BLOCK_TAGS.has(name)) text += '\\n';\n },\n ontext(data) {\n if (!ignoring) text += data;\n },\n },\n { decodeEntities: true },\n );\n\n parser.write(html);\n parser.end();\n\n return text\n .replace(/\\n\\s*\\n/g, '\\n\\n')\n .replace(/[ \\t]+/g, ' ')\n .trim();\n};\n\n/**\n * Sanitize content to prevent prompt injection attacks\n * Detects and filters common prompt injection patterns\n * @param content - Raw content to sanitize\n * @returns Sanitized content with prompt injection patterns removed\n * @reserved Reserved for future URL file type support\n */\nexport const sanitizeContentForRAG = (content: string): string => {\n let sanitized = content;\n\n // Replace prompt injection patterns with filtered marker\n for (const pattern of PROMPT_INJECTION_PATTERNS) {\n sanitized = sanitized.replace(pattern, FILTERED_CONTENT_MARKER);\n }\n\n // Limit excessive consecutive newlines to prevent context stuffing\n const newlinePattern = new RegExp(\n `\\\\n{${MAX_CONSECUTIVE_NEWLINES + 1},}`,\n 'g',\n );\n sanitized = sanitized.replace(\n newlinePattern,\n '\\n'.repeat(MAX_CONSECUTIVE_NEWLINES),\n );\n\n // Normalize excessive whitespace\n sanitized = sanitized.replace(/[ \\t]{10,}/g, ' ');\n\n return sanitized.trim();\n};\n"],"names":["DEFAULT_MAX_FILE_SIZE_MB","SupportedFileType","InputError","parseFile"],"mappings":";;;;;;;;;AAiEO,MAAM,eAAA,GAAkB,CAAC,QAAA,KAA8B;AAC5D,EAAA,MAAM,OAAA,GAAUA,iCAAA;AAChB,EAAA,OAAO,QAAA,IAAY,OAAA;AACrB;AAKO,MAAM,eAAA,GAAkB,CAAC,QAAA,KAA8B;AAC5D,EAAA,MAAM,iBAAiB,QAAA,CAAS,WAAA,EAAY,CAAE,OAAA,CAAQ,OAAO,EAAE,CAAA;AAC/D,EAAA,OAAO,MAAA,CAAO,MAAA,CAAOC,0BAAiB,CAAA,CAAE,QAAA;AAAA,IACtC;AAAA,GACF;AACF;AASO,MAAM,gBAAA,GAAmB,OAC9B,MAAA,EACA,QAAA,EACA,IAAA,KACG;AACH,EAAA,IAAI,CAAC,IAAA,EAAM;AACT,IAAA,MAAM,IAAIC,kBAAW,kBAAkB,CAAA;AAAA,EACzC;AACA,EAAA,IAAI,CAAC,eAAA,CAAgB,IAAA,CAAK,IAAI,CAAA,EAAG;AAC/B,IAAA,MAAM,IAAIA,iBAAA;AAAA,MACR,CAAA,kBAAA,EAAqBF,iCAAA,GAA2B,IAAA,GAAO,IAAI,CAAA,QAAA;AAAA,KAC7D;AAAA,EACF;AACA,EAAA,MAAA,CAAO,KAAK,CAAA,aAAA,EAAgB,IAAA,CAAK,YAAY,CAAA,cAAA,EAAiB,QAAQ,CAAA,CAAE,CAAA;AACxE,EAAA,OAAO,MAAMG,oBAAA,CAAU,IAAA,CAAK,MAAA,EAAQ,IAAA,CAAK,cAAc,QAAQ,CAAA;AACjE;;;;;;"}