Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@red-hat-developer-hub/backstage-plugin-lightspeed-backend': minor

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[high] changeset-target-error

Changeset targets '@red-hat-developer-hub/backstage-plugin-lightspeed-backend', which is not a package in this repository. The actual package is '@red-hat-developer-hub/backstage-plugin-intelligent-assistant-backend'. This changeset will either be silently ignored or cause a build error during release.

Suggested fix: Change the package name in the changeset to '@red-hat-developer-hub/backstage-plugin-intelligent-assistant-backend'.

---

bugfix - Notebooks routes 404 passthrough error resolved
Comment on lines +1 to +5

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we including this bug fix in this PR and I have missed it? Or is this leftover and needs to be removed to avoid another minor bump?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets remove this changeset if it is unused/not included in this PR to keep a clean history

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@red-hat-developer-hub/backstage-plugin-intelligent-assistant-backend': minor

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be a patch if it is a bugfix

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say this is more of an enhancement, so that's why I put it as update, because I'm switching out a component to improve vector stores

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm on the side of if it isn't client facing / just internal then it should be a 'patch' but I will leave it up to you

---

AI Notebooks will use markitdown to clean up data before vectorizing documents
Original file line number Diff line number Diff line change
Expand Up @@ -54,20 +54,17 @@
"express": "^4.21.1",
"express-rate-limit": "^8.2.2",
"form-data": "^4.0.6",
"htmlparser2": "^9.1.0",
"http-proxy-middleware": "^3.0.7",
"js-yaml": "^4.1.1",
"knex": "^3.1.0",
"multer": "^2.2.0",
"pdfjs-dist": "^4.10.38"
"markitdown-ts": "^0.0.10",
"multer": "^2.2.0"
},
"devDependencies": {
"@backstage/backend-test-utils": "^1.11.4",
"@backstage/cli": "^0.36.3",
"@ianvs/prettier-plugin-sort-imports": "^4.4.0",
"@spotify/prettier-config": "^15.0.0",
"@types/express": "4.17.25",
"@types/js-yaml": "^4",
"@types/multer": "^1.4.12",
"@types/supertest": "2.0.16",
"msw": "2.14.6",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,10 @@
* limitations under the License.
*/

import { mockServices } from '@backstage/backend-test-utils';

import { DEFAULT_MAX_FILE_SIZE_MB } from '../../constant';
import {
isValidFileSize,
isValidFileType,
parseFileContent,
} from './documentHelpers';
import { isValidFileSize, isValidFileType } from './documentHelpers';

describe('documentHelpers', () => {
const logger = mockServices.logger.mock();

afterEach(() => {
jest.clearAllMocks();
});

describe('isValidFileSize', () => {
const MB = 1024 * 1024;

Expand Down Expand Up @@ -93,38 +81,4 @@ describe('documentHelpers', () => {
expect(isValidFileType('.')).toBe(false);
});
});

describe('parseFileContent', () => {
it('should throw error when no file uploaded for non-URL type', async () => {
await expect(parseFileContent(logger, 'txt', undefined)).rejects.toThrow(
'No file uploaded',
);
});

it('should throw error when file size exceeds limit', async () => {
const largeFile = {
buffer: Buffer.from('test'),
originalname: 'large.txt',
size: 21 * 1024 * 1024, // 21MB
} as Express.Multer.File;

await expect(parseFileContent(logger, 'txt', largeFile)).rejects.toThrow(
`File size exceeds ${DEFAULT_MAX_FILE_SIZE_MB / 1024 / 1024}MB limit`,
);
});

it('should parse valid file successfully', async () => {
const file = {
buffer: Buffer.from('test content'),
originalname: 'test.txt',
size: 1024,
} as Express.Multer.File;

const result = await parseFileContent(logger, 'txt', file);

expect(result.content).toBe('test content');
expect(result.metadata.fileName).toBe('test.txt');
expect(result.metadata.fileType).toBe('txt');
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -13,25 +13,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { LoggerService } from '@backstage/backend-plugin-api';
import { InputError } from '@backstage/errors';

import { Parser } from 'htmlparser2';

import dns from 'dns/promises';
import { isIP } from 'net';

import {
DEFAULT_MAX_FILE_SIZE_MB,
FILTERED_CONTENT_MARKER,
HTML_BLOCK_TAGS,
HTML_IGNORED_TAGS,
MAX_CONSECUTIVE_NEWLINES,
PROMPT_INJECTION_PATTERNS,
SSRF_BLOCKED_HOSTNAMES,
SupportedFileType,
} from '../../constant';
import { parseFile } from './fileParser';

// ==============================================================================
// URL Support Functions (Reserved for Future URL File Type Support)
Expand Down Expand Up @@ -78,30 +72,6 @@ export const isValidFileType = (fileType: string): boolean => {
);
};

/**
* Parse file from upload
* @param logger - Logger service
* @param fileType - File type
* @param file - File to parse
* @returns Parsed file
*/
export const parseFileContent = async (
logger: LoggerService,
fileType: string,
file?: Express.Multer.File,
) => {
if (!file) {
throw new InputError('No file uploaded');
}
if (!isValidFileSize(file.size)) {
throw new InputError(
`File size exceeds ${DEFAULT_MAX_FILE_SIZE_MB / 1024 / 1024}MB limit`,
);
}
logger.info(`Parsing file ${file.originalname} for fileType ${fileType}`);
return await parseFile(file.buffer, file.originalname, fileType);
};

/**
* Check if an IP address is private, internal, or a metadata endpoint
* Blocks SSRF attacks to internal networks
Expand Down Expand Up @@ -243,39 +213,6 @@ export const validateURLForSSRF = async (urlString: string): Promise<void> => {
}
};

/**
* Strip HTML tags and extract readable text from HTML content
* @reserved Reserved for future URL file type support
*/
export const stripHtmlTags = (html: string): string => {
let text = '';
let ignoring = false;

const parser = new Parser(
{
onopentag(name) {
if (HTML_IGNORED_TAGS.has(name)) ignoring = true;
},
onclosetag(name) {
if (HTML_IGNORED_TAGS.has(name)) ignoring = false;
if (HTML_BLOCK_TAGS.has(name)) text += '\n';
},
ontext(data) {
if (!ignoring) text += data;
},
},
{ decodeEntities: true },
);

parser.write(html);
parser.end();

return text
.replace(/\n\s*\n/g, '\n\n')
.replace(/[ \t]+/g, ' ')
.trim();
};

/**
* Sanitize content to prevent prompt injection attacks
* Detects and filters common prompt injection patterns
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import {
} from '../../constant';
import { SessionDocument, UpsertResult } from '../types/notebooksTypes';
import { VectorStoresOperator } from '../VectorStoresOperator';
import { toFile } from './fileParser';

/**
* Service for managing documents within notebook sessions using File-Based API
Expand Down Expand Up @@ -92,21 +91,22 @@ export class DocumentService {
}

/**
* Upload a file to the Files API
* @param content - File content as string
* Upload markdown content to the Files API
* @param content - Markdown content string
* @param title - File title/name
* @returns File ID from the Files API
* @throws Error if upload fails
*/
async uploadFile(content: string, title: string): Promise<string> {
try {
// Determine MIME type from file type or default to text/plain
const mimeType = 'text/plain';
const txtFilename = `${title.replace(/\.[^.]+$/, '')}.txt`;
const mdFilename = `${title.replace(/\.[^.]+$/, '')}.md`;

const file = await this.client.files.create({
file: await toFile(Buffer.from(content, 'utf-8'), txtFilename, {
type: mimeType,
}),
file: {
name: mdFilename,
buffer: Buffer.from(content, 'utf-8'),
type: 'text/markdown',
},
purpose: 'assistants',
});

Expand All @@ -115,11 +115,9 @@ export class DocumentService {
);
return file.id;
} catch (error) {
// Preserve the original error type and message
if (error instanceof Error) {
throw error;
}
// For non-Error objects, wrap with context
throw new Error(`Failed to upload file: ${String(error)}`);
}
}
Expand Down
Loading
Loading