| import { primeResources } from './resources'; |
| import { logger } from '@librechat/data-schemas'; |
| import { EModelEndpoint, EToolResources, AgentCapabilities } from 'librechat-data-provider'; |
| import type { TAgentsEndpoint, TFile } from 'librechat-data-provider'; |
| import type { IUser, AppConfig } from '@librechat/data-schemas'; |
| import type { Request as ServerRequest } from 'express'; |
| import type { TGetFiles } from './resources'; |
|
|
| |
| jest.mock('@librechat/data-schemas', () => ({ |
| logger: { |
| error: jest.fn(), |
| }, |
| })); |
|
|
| describe('primeResources', () => { |
| let mockReq: ServerRequest & { user?: IUser }; |
| let mockAppConfig: AppConfig; |
| let mockGetFiles: jest.MockedFunction<TGetFiles>; |
| let requestFileSet: Set<string>; |
|
|
| beforeEach(() => { |
| |
| jest.clearAllMocks(); |
|
|
| |
| mockReq = {} as unknown as ServerRequest & { user?: IUser }; |
|
|
| |
| mockAppConfig = { |
| endpoints: { |
| [EModelEndpoint.agents]: { |
| capabilities: [AgentCapabilities.context], |
| } as TAgentsEndpoint, |
| }, |
| } as AppConfig; |
|
|
| |
| mockGetFiles = jest.fn(); |
|
|
| |
| requestFileSet = new Set(['file1', 'file2', 'file3']); |
| }); |
|
|
| describe('when `context` capability is enabled and tool_resources has "context" file_ids', () => { |
| it('should fetch context files and include them in attachments', async () => { |
| const mockOcrFiles: TFile[] = [ |
| { |
| user: 'user1', |
| file_id: 'ocr-file-1', |
| filename: 'document.pdf', |
| filepath: '/uploads/document.pdf', |
| object: 'file', |
| type: 'application/pdf', |
| bytes: 1024, |
| embedded: false, |
| usage: 0, |
| }, |
| ]; |
|
|
| mockGetFiles.mockResolvedValue(mockOcrFiles); |
|
|
| const tool_resources = { |
| [EToolResources.context]: { |
| file_ids: ['ocr-file-1'], |
| }, |
| }; |
|
|
| const result = await primeResources({ |
| req: mockReq, |
| appConfig: mockAppConfig, |
| getFiles: mockGetFiles, |
| requestFileSet, |
| attachments: undefined, |
| tool_resources, |
| }); |
|
|
| expect(mockGetFiles).toHaveBeenCalledWith( |
| { file_id: { $in: ['ocr-file-1'] } }, |
| {}, |
| {}, |
| { userId: undefined, agentId: undefined }, |
| ); |
| expect(result.attachments).toEqual(mockOcrFiles); |
| |
| |
| expect(result.tool_resources).toEqual({}); |
| }); |
| }); |
|
|
| describe('when `context` capability is disabled', () => { |
| it('should not fetch context files even if tool_resources has context file_ids', async () => { |
| (mockAppConfig.endpoints![EModelEndpoint.agents] as TAgentsEndpoint).capabilities = []; |
|
|
| const tool_resources = { |
| [EToolResources.context]: { |
| file_ids: ['ocr-file-1'], |
| }, |
| }; |
|
|
| const result = await primeResources({ |
| req: mockReq, |
| appConfig: mockAppConfig, |
| getFiles: mockGetFiles, |
| requestFileSet, |
| attachments: undefined, |
| tool_resources, |
| }); |
|
|
| expect(mockGetFiles).not.toHaveBeenCalled(); |
| expect(result.attachments).toBeUndefined(); |
| expect(result.tool_resources).toEqual(tool_resources); |
| }); |
| }); |
|
|
| describe('when attachments are provided', () => { |
| it('should process files with fileIdentifier as execute_code resources', async () => { |
| const mockFiles: TFile[] = [ |
| { |
| user: 'user1', |
| file_id: 'file1', |
| filename: 'script.py', |
| filepath: '/uploads/script.py', |
| object: 'file', |
| type: 'text/x-python', |
| bytes: 512, |
| embedded: false, |
| usage: 0, |
| metadata: { |
| fileIdentifier: 'python-script', |
| }, |
| }, |
| ]; |
|
|
| const attachments = Promise.resolve(mockFiles); |
|
|
| const result = await primeResources({ |
| req: mockReq, |
| appConfig: mockAppConfig, |
| getFiles: mockGetFiles, |
| requestFileSet, |
| attachments, |
| tool_resources: {}, |
| }); |
|
|
| expect(result.attachments).toEqual(mockFiles); |
| expect(result.tool_resources?.[EToolResources.execute_code]?.files).toEqual(mockFiles); |
| }); |
|
|
| it('should process embedded files as file_search resources', async () => { |
| const mockFiles: TFile[] = [ |
| { |
| user: 'user1', |
| file_id: 'file2', |
| filename: 'document.txt', |
| filepath: '/uploads/document.txt', |
| object: 'file', |
| type: 'text/plain', |
| bytes: 256, |
| embedded: true, |
| usage: 0, |
| }, |
| ]; |
|
|
| const attachments = Promise.resolve(mockFiles); |
|
|
| const result = await primeResources({ |
| req: mockReq, |
| appConfig: mockAppConfig, |
| getFiles: mockGetFiles, |
| requestFileSet, |
| attachments, |
| tool_resources: {}, |
| }); |
|
|
| expect(result.attachments).toEqual(mockFiles); |
| expect(result.tool_resources?.[EToolResources.file_search]?.files).toEqual(mockFiles); |
| }); |
|
|
| it('should process image files in requestFileSet as image_edit resources', async () => { |
| const mockFiles: TFile[] = [ |
| { |
| user: 'user1', |
| file_id: 'file1', |
| filename: 'image.png', |
| filepath: '/uploads/image.png', |
| object: 'file', |
| type: 'image/png', |
| bytes: 2048, |
| embedded: false, |
| usage: 0, |
| height: 800, |
| width: 600, |
| }, |
| ]; |
|
|
| const attachments = Promise.resolve(mockFiles); |
|
|
| const result = await primeResources({ |
| req: mockReq, |
| appConfig: mockAppConfig, |
| getFiles: mockGetFiles, |
| requestFileSet, |
| attachments, |
| tool_resources: {}, |
| }); |
|
|
| expect(result.attachments).toEqual(mockFiles); |
| expect(result.tool_resources?.[EToolResources.image_edit]?.files).toEqual(mockFiles); |
| }); |
|
|
| it('should not process image files not in requestFileSet', async () => { |
| const mockFiles: TFile[] = [ |
| { |
| user: 'user1', |
| file_id: 'file-not-in-set', |
| filename: 'image.png', |
| filepath: '/uploads/image.png', |
| object: 'file', |
| type: 'image/png', |
| bytes: 2048, |
| embedded: false, |
| usage: 0, |
| height: 800, |
| width: 600, |
| }, |
| ]; |
|
|
| const attachments = Promise.resolve(mockFiles); |
|
|
| const result = await primeResources({ |
| req: mockReq, |
| appConfig: mockAppConfig, |
| getFiles: mockGetFiles, |
| requestFileSet, |
| attachments, |
| tool_resources: {}, |
| }); |
|
|
| expect(result.attachments).toEqual(mockFiles); |
| expect(result.tool_resources?.[EToolResources.image_edit]).toBeUndefined(); |
| }); |
|
|
| it('should not process image files without height and width', async () => { |
| const mockFiles: TFile[] = [ |
| { |
| user: 'user1', |
| file_id: 'file1', |
| filename: 'image.png', |
| filepath: '/uploads/image.png', |
| object: 'file', |
| type: 'image/png', |
| bytes: 2048, |
| embedded: false, |
| usage: 0, |
| |
| }, |
| ]; |
|
|
| const attachments = Promise.resolve(mockFiles); |
|
|
| const result = await primeResources({ |
| req: mockReq, |
| appConfig: mockAppConfig, |
| getFiles: mockGetFiles, |
| requestFileSet, |
| attachments, |
| tool_resources: {}, |
| }); |
|
|
| expect(result.attachments).toEqual(mockFiles); |
| expect(result.tool_resources?.[EToolResources.image_edit]).toBeUndefined(); |
| }); |
|
|
| it('should filter out null files from attachments', async () => { |
| const mockFiles: Array<TFile | null> = [ |
| { |
| user: 'user1', |
| file_id: 'file1', |
| filename: 'valid.txt', |
| filepath: '/uploads/valid.txt', |
| object: 'file', |
| type: 'text/plain', |
| bytes: 256, |
| embedded: false, |
| usage: 0, |
| }, |
| null, |
| { |
| user: 'user1', |
| file_id: 'file2', |
| filename: 'valid2.txt', |
| filepath: '/uploads/valid2.txt', |
| object: 'file', |
| type: 'text/plain', |
| bytes: 128, |
| embedded: false, |
| usage: 0, |
| }, |
| ]; |
|
|
| const attachments = Promise.resolve(mockFiles); |
|
|
| const result = await primeResources({ |
| req: mockReq, |
| appConfig: mockAppConfig, |
| getFiles: mockGetFiles, |
| requestFileSet, |
| attachments, |
| tool_resources: {}, |
| }); |
|
|
| expect(result.attachments).toHaveLength(2); |
| expect(result.attachments?.[0]?.file_id).toBe('file1'); |
| expect(result.attachments?.[1]?.file_id).toBe('file2'); |
| }); |
|
|
| it('should merge existing tool_resources with new files', async () => { |
| const mockFiles: TFile[] = [ |
| { |
| user: 'user1', |
| file_id: 'file1', |
| filename: 'script.py', |
| filepath: '/uploads/script.py', |
| object: 'file', |
| type: 'text/x-python', |
| bytes: 512, |
| embedded: false, |
| usage: 0, |
| metadata: { |
| fileIdentifier: 'python-script', |
| }, |
| }, |
| ]; |
|
|
| const existingToolResources = { |
| [EToolResources.execute_code]: { |
| files: [ |
| { |
| user: 'user1', |
| file_id: 'existing-file', |
| filename: 'existing.py', |
| filepath: '/uploads/existing.py', |
| object: 'file' as const, |
| type: 'text/x-python', |
| bytes: 256, |
| embedded: false, |
| usage: 0, |
| }, |
| ], |
| }, |
| }; |
|
|
| const attachments = Promise.resolve(mockFiles); |
|
|
| const result = await primeResources({ |
| req: mockReq, |
| appConfig: mockAppConfig, |
| getFiles: mockGetFiles, |
| requestFileSet, |
| attachments, |
| tool_resources: existingToolResources, |
| }); |
|
|
| expect(result.tool_resources?.[EToolResources.execute_code]?.files).toHaveLength(2); |
| expect(result.tool_resources?.[EToolResources.execute_code]?.files?.[0]?.file_id).toBe( |
| 'existing-file', |
| ); |
| expect(result.tool_resources?.[EToolResources.execute_code]?.files?.[1]?.file_id).toBe( |
| 'file1', |
| ); |
| }); |
| }); |
|
|
| describe('when both "context" files and "attachments" are provided', () => { |
| it('should include both context files and attachment files', async () => { |
| const mockOcrFiles: TFile[] = [ |
| { |
| user: 'user1', |
| file_id: 'ocr-file-1', |
| filename: 'document.pdf', |
| filepath: '/uploads/document.pdf', |
| object: 'file', |
| type: 'application/pdf', |
| bytes: 1024, |
| embedded: false, |
| usage: 0, |
| }, |
| ]; |
|
|
| const mockAttachmentFiles: TFile[] = [ |
| { |
| user: 'user1', |
| file_id: 'file1', |
| filename: 'attachment.txt', |
| filepath: '/uploads/attachment.txt', |
| object: 'file', |
| type: 'text/plain', |
| bytes: 256, |
| embedded: false, |
| usage: 0, |
| }, |
| ]; |
|
|
| mockGetFiles.mockResolvedValue(mockOcrFiles); |
| const attachments = Promise.resolve(mockAttachmentFiles); |
|
|
| const tool_resources = { |
| [EToolResources.context]: { |
| file_ids: ['ocr-file-1'], |
| }, |
| }; |
|
|
| const result = await primeResources({ |
| req: mockReq, |
| appConfig: mockAppConfig, |
| getFiles: mockGetFiles, |
| requestFileSet, |
| attachments, |
| tool_resources, |
| }); |
|
|
| expect(result.attachments).toHaveLength(2); |
| expect(result.attachments?.[0]?.file_id).toBe('ocr-file-1'); |
| expect(result.attachments?.[1]?.file_id).toBe('file1'); |
| }); |
|
|
| it('should include both context (as `ocr` resource) files and attachment files', async () => { |
| const mockOcrFiles: TFile[] = [ |
| { |
| user: 'user1', |
| file_id: 'ocr-file-1', |
| filename: 'document.pdf', |
| filepath: '/uploads/document.pdf', |
| object: 'file', |
| type: 'application/pdf', |
| bytes: 1024, |
| embedded: false, |
| usage: 0, |
| }, |
| ]; |
|
|
| const mockAttachmentFiles: TFile[] = [ |
| { |
| user: 'user1', |
| file_id: 'file1', |
| filename: 'attachment.txt', |
| filepath: '/uploads/attachment.txt', |
| object: 'file', |
| type: 'text/plain', |
| bytes: 256, |
| embedded: false, |
| usage: 0, |
| }, |
| ]; |
|
|
| mockGetFiles.mockResolvedValue(mockOcrFiles); |
| const attachments = Promise.resolve(mockAttachmentFiles); |
|
|
| const tool_resources = { |
| [EToolResources.ocr]: { |
| file_ids: ['ocr-file-1'], |
| }, |
| }; |
|
|
| const result = await primeResources({ |
| req: mockReq, |
| appConfig: mockAppConfig, |
| getFiles: mockGetFiles, |
| requestFileSet, |
| attachments, |
| tool_resources, |
| }); |
|
|
| expect(result.attachments).toHaveLength(2); |
| expect(result.attachments?.[0]?.file_id).toBe('ocr-file-1'); |
| expect(result.attachments?.[1]?.file_id).toBe('file1'); |
| }); |
|
|
| it('should prevent duplicate files when same file exists in context tool_resource and attachments', async () => { |
| const sharedFile: TFile = { |
| user: 'user1', |
| file_id: 'shared-file-id', |
| filename: 'document.pdf', |
| filepath: '/uploads/document.pdf', |
| object: 'file', |
| type: 'application/pdf', |
| bytes: 1024, |
| embedded: false, |
| usage: 0, |
| }; |
|
|
| const mockOcrFiles: TFile[] = [sharedFile]; |
| const mockAttachmentFiles: TFile[] = [ |
| sharedFile, |
| { |
| user: 'user1', |
| file_id: 'unique-file', |
| filename: 'other.txt', |
| filepath: '/uploads/other.txt', |
| object: 'file', |
| type: 'text/plain', |
| bytes: 256, |
| embedded: false, |
| usage: 0, |
| }, |
| ]; |
|
|
| mockGetFiles.mockResolvedValue(mockOcrFiles); |
| const attachments = Promise.resolve(mockAttachmentFiles); |
|
|
| const tool_resources = { |
| [EToolResources.context]: { |
| file_ids: ['shared-file-id'], |
| }, |
| }; |
|
|
| const result = await primeResources({ |
| req: mockReq, |
| appConfig: mockAppConfig, |
| getFiles: mockGetFiles, |
| requestFileSet, |
| attachments, |
| tool_resources, |
| }); |
|
|
| |
| expect(result.attachments).toHaveLength(2); |
| expect(result.attachments?.filter((f) => f?.file_id === 'shared-file-id')).toHaveLength(1); |
| expect(result.attachments?.find((f) => f?.file_id === 'unique-file')).toBeDefined(); |
| }); |
|
|
| it('should still categorize duplicate files for tool_resources', async () => { |
| const sharedFile: TFile = { |
| user: 'user1', |
| file_id: 'shared-file-id', |
| filename: 'script.py', |
| filepath: '/uploads/script.py', |
| object: 'file', |
| type: 'text/x-python', |
| bytes: 512, |
| embedded: false, |
| usage: 0, |
| metadata: { |
| fileIdentifier: 'python-script', |
| }, |
| }; |
|
|
| const mockOcrFiles: TFile[] = [sharedFile]; |
| const mockAttachmentFiles: TFile[] = [sharedFile]; |
|
|
| mockGetFiles.mockResolvedValue(mockOcrFiles); |
| const attachments = Promise.resolve(mockAttachmentFiles); |
|
|
| const tool_resources = { |
| [EToolResources.context]: { |
| file_ids: ['shared-file-id'], |
| }, |
| }; |
|
|
| const result = await primeResources({ |
| req: mockReq, |
| appConfig: mockAppConfig, |
| getFiles: mockGetFiles, |
| requestFileSet, |
| attachments, |
| tool_resources, |
| }); |
|
|
| |
| expect(result.attachments).toHaveLength(1); |
| expect(result.attachments?.[0]?.file_id).toBe('shared-file-id'); |
|
|
| |
| expect(result.tool_resources?.[EToolResources.execute_code]?.files).toHaveLength(1); |
| expect(result.tool_resources?.[EToolResources.execute_code]?.files?.[0]?.file_id).toBe( |
| 'shared-file-id', |
| ); |
| }); |
|
|
| it('should handle multiple duplicate files', async () => { |
| const file1: TFile = { |
| user: 'user1', |
| file_id: 'file-1', |
| filename: 'doc1.pdf', |
| filepath: '/uploads/doc1.pdf', |
| object: 'file', |
| type: 'application/pdf', |
| bytes: 1024, |
| embedded: false, |
| usage: 0, |
| }; |
|
|
| const file2: TFile = { |
| user: 'user1', |
| file_id: 'file-2', |
| filename: 'doc2.pdf', |
| filepath: '/uploads/doc2.pdf', |
| object: 'file', |
| type: 'application/pdf', |
| bytes: 2048, |
| embedded: false, |
| usage: 0, |
| }; |
|
|
| const uniqueFile: TFile = { |
| user: 'user1', |
| file_id: 'unique-file', |
| filename: 'unique.txt', |
| filepath: '/uploads/unique.txt', |
| object: 'file', |
| type: 'text/plain', |
| bytes: 256, |
| embedded: false, |
| usage: 0, |
| }; |
|
|
| const mockOcrFiles: TFile[] = [file1, file2]; |
| const mockAttachmentFiles: TFile[] = [file1, file2, uniqueFile]; |
|
|
| mockGetFiles.mockResolvedValue(mockOcrFiles); |
| const attachments = Promise.resolve(mockAttachmentFiles); |
|
|
| const tool_resources = { |
| [EToolResources.context]: { |
| file_ids: ['file-1', 'file-2'], |
| }, |
| }; |
|
|
| const result = await primeResources({ |
| req: mockReq, |
| appConfig: mockAppConfig, |
| getFiles: mockGetFiles, |
| requestFileSet, |
| attachments, |
| tool_resources, |
| }); |
|
|
| |
| expect(result.attachments).toHaveLength(3); |
|
|
| |
| const fileIds = result.attachments?.map((f) => f?.file_id); |
| expect(fileIds).toContain('file-1'); |
| expect(fileIds).toContain('file-2'); |
| expect(fileIds).toContain('unique-file'); |
|
|
| |
| const uniqueFileIds = new Set(fileIds); |
| expect(uniqueFileIds.size).toBe(fileIds?.length); |
| }); |
|
|
| it('should handle files without file_id gracefully', async () => { |
| const fileWithoutId: Partial<TFile> = { |
| user: 'user1', |
| filename: 'no-id.txt', |
| filepath: '/uploads/no-id.txt', |
| object: 'file', |
| type: 'text/plain', |
| bytes: 256, |
| embedded: false, |
| usage: 0, |
| }; |
|
|
| const normalFile: TFile = { |
| user: 'user1', |
| file_id: 'normal-file', |
| filename: 'normal.txt', |
| filepath: '/uploads/normal.txt', |
| object: 'file', |
| type: 'text/plain', |
| bytes: 512, |
| embedded: false, |
| usage: 0, |
| }; |
|
|
| const mockOcrFiles: TFile[] = [normalFile]; |
| const mockAttachmentFiles = [fileWithoutId as TFile, normalFile]; |
|
|
| mockGetFiles.mockResolvedValue(mockOcrFiles); |
| const attachments = Promise.resolve(mockAttachmentFiles); |
|
|
| const tool_resources = { |
| [EToolResources.context]: { |
| file_ids: ['normal-file'], |
| }, |
| }; |
|
|
| const result = await primeResources({ |
| req: mockReq, |
| appConfig: mockAppConfig, |
| getFiles: mockGetFiles, |
| requestFileSet, |
| attachments, |
| tool_resources, |
| }); |
|
|
| |
| expect(result.attachments).toHaveLength(2); |
| expect(result.attachments?.filter((f) => f?.file_id === 'normal-file')).toHaveLength(1); |
| expect(result.attachments?.some((f) => !f?.file_id)).toBe(true); |
| }); |
|
|
| it('should prevent duplicates from existing tool_resources', async () => { |
| const existingFile: TFile = { |
| user: 'user1', |
| file_id: 'existing-file', |
| filename: 'existing.py', |
| filepath: '/uploads/existing.py', |
| object: 'file', |
| type: 'text/x-python', |
| bytes: 512, |
| embedded: false, |
| usage: 0, |
| metadata: { |
| fileIdentifier: 'python-script', |
| }, |
| }; |
|
|
| const newFile: TFile = { |
| user: 'user1', |
| file_id: 'new-file', |
| filename: 'new.py', |
| filepath: '/uploads/new.py', |
| object: 'file', |
| type: 'text/x-python', |
| bytes: 256, |
| embedded: false, |
| usage: 0, |
| metadata: { |
| fileIdentifier: 'python-script', |
| }, |
| }; |
|
|
| const existingToolResources = { |
| [EToolResources.execute_code]: { |
| files: [existingFile], |
| }, |
| }; |
|
|
| const attachments = Promise.resolve([existingFile, newFile]); |
|
|
| const result = await primeResources({ |
| req: mockReq, |
| appConfig: mockAppConfig, |
| getFiles: mockGetFiles, |
| requestFileSet, |
| attachments, |
| tool_resources: existingToolResources, |
| }); |
|
|
| |
| expect(result.attachments).toHaveLength(1); |
| expect(result.attachments?.[0]?.file_id).toBe('new-file'); |
|
|
| |
| expect(result.tool_resources?.[EToolResources.execute_code]?.files).toHaveLength(2); |
| const fileIds = result.tool_resources?.[EToolResources.execute_code]?.files?.map( |
| (f) => f.file_id, |
| ); |
| expect(fileIds).toEqual(['existing-file', 'new-file']); |
| }); |
|
|
| it('should handle duplicates within attachments array', async () => { |
| const duplicatedFile: TFile = { |
| user: 'user1', |
| file_id: 'dup-file', |
| filename: 'duplicate.txt', |
| filepath: '/uploads/duplicate.txt', |
| object: 'file', |
| type: 'text/plain', |
| bytes: 256, |
| embedded: false, |
| usage: 0, |
| }; |
|
|
| const uniqueFile: TFile = { |
| user: 'user1', |
| file_id: 'unique-file', |
| filename: 'unique.txt', |
| filepath: '/uploads/unique.txt', |
| object: 'file', |
| type: 'text/plain', |
| bytes: 128, |
| embedded: false, |
| usage: 0, |
| }; |
|
|
| |
| const attachments = Promise.resolve([ |
| duplicatedFile, |
| duplicatedFile, |
| uniqueFile, |
| duplicatedFile, |
| ]); |
|
|
| const result = await primeResources({ |
| req: mockReq, |
| appConfig: mockAppConfig, |
| getFiles: mockGetFiles, |
| requestFileSet, |
| attachments, |
| tool_resources: {}, |
| }); |
|
|
| |
| expect(result.attachments).toHaveLength(2); |
| const fileIds = result.attachments?.map((f) => f?.file_id); |
| expect(fileIds).toContain('dup-file'); |
| expect(fileIds).toContain('unique-file'); |
|
|
| |
| expect(fileIds?.filter((id) => id === 'dup-file')).toHaveLength(1); |
| }); |
|
|
| it('should prevent duplicates across different tool_resource categories', async () => { |
| const multiPurposeFile: TFile = { |
| user: 'user1', |
| file_id: 'multi-file', |
| filename: 'data.txt', |
| filepath: '/uploads/data.txt', |
| object: 'file', |
| type: 'text/plain', |
| bytes: 512, |
| embedded: true, |
| usage: 0, |
| }; |
|
|
| const existingToolResources = { |
| [EToolResources.file_search]: { |
| files: [multiPurposeFile], |
| }, |
| }; |
|
|
| |
| const attachments = Promise.resolve([multiPurposeFile]); |
|
|
| const result = await primeResources({ |
| req: mockReq, |
| appConfig: mockAppConfig, |
| getFiles: mockGetFiles, |
| requestFileSet, |
| attachments, |
| tool_resources: existingToolResources, |
| }); |
|
|
| |
| expect(result.attachments).toHaveLength(0); |
|
|
| |
| expect(result.tool_resources?.[EToolResources.file_search]?.files).toHaveLength(1); |
| expect(result.tool_resources?.[EToolResources.file_search]?.files?.[0]?.file_id).toBe( |
| 'multi-file', |
| ); |
| }); |
|
|
| it('should handle complex scenario with context files, existing tool_resources, and attachments', async () => { |
| const ocrFile: TFile = { |
| user: 'user1', |
| file_id: 'ocr-file', |
| filename: 'scan.pdf', |
| filepath: '/uploads/scan.pdf', |
| object: 'file', |
| type: 'application/pdf', |
| bytes: 2048, |
| embedded: false, |
| usage: 0, |
| }; |
|
|
| const existingFile: TFile = { |
| user: 'user1', |
| file_id: 'existing-file', |
| filename: 'code.py', |
| filepath: '/uploads/code.py', |
| object: 'file', |
| type: 'text/x-python', |
| bytes: 512, |
| embedded: false, |
| usage: 0, |
| metadata: { |
| fileIdentifier: 'python-script', |
| }, |
| }; |
|
|
| const newFile: TFile = { |
| user: 'user1', |
| file_id: 'new-file', |
| filename: 'image.png', |
| filepath: '/uploads/image.png', |
| object: 'file', |
| type: 'image/png', |
| bytes: 4096, |
| embedded: false, |
| usage: 0, |
| height: 800, |
| width: 600, |
| }; |
|
|
| mockGetFiles.mockResolvedValue([ocrFile, existingFile]); |
| const attachments = Promise.resolve([existingFile, ocrFile, newFile]); |
|
|
| const existingToolResources = { |
| [EToolResources.context]: { |
| file_ids: ['ocr-file', 'existing-file'], |
| }, |
| [EToolResources.execute_code]: { |
| files: [existingFile], |
| }, |
| }; |
|
|
| requestFileSet.add('new-file'); |
|
|
| const result = await primeResources({ |
| req: mockReq, |
| appConfig: mockAppConfig, |
| getFiles: mockGetFiles, |
| requestFileSet, |
| attachments, |
| tool_resources: existingToolResources, |
| }); |
|
|
| |
| expect(result.attachments).toHaveLength(3); |
| const attachmentIds = result.attachments?.map((f) => f?.file_id).sort(); |
| expect(attachmentIds).toEqual(['existing-file', 'new-file', 'ocr-file']); |
|
|
| |
| expect(result.tool_resources?.[EToolResources.execute_code]?.files).toHaveLength(1); |
| expect(result.tool_resources?.[EToolResources.image_edit]?.files).toHaveLength(1); |
| expect(result.tool_resources?.[EToolResources.image_edit]?.files?.[0]?.file_id).toBe( |
| 'new-file', |
| ); |
| }); |
| }); |
|
|
| describe('error handling', () => { |
| it('should handle errors gracefully and log them', async () => { |
| const mockFiles: TFile[] = [ |
| { |
| user: 'user1', |
| file_id: 'file1', |
| filename: 'test.txt', |
| filepath: '/uploads/test.txt', |
| object: 'file', |
| type: 'text/plain', |
| bytes: 256, |
| embedded: false, |
| usage: 0, |
| }, |
| ]; |
|
|
| const attachments = Promise.resolve(mockFiles); |
| const error = new Error('Test error'); |
|
|
| |
| mockGetFiles.mockRejectedValue(error); |
|
|
| const tool_resources = { |
| [EToolResources.context]: { |
| file_ids: ['ocr-file-1'], |
| }, |
| }; |
|
|
| const result = await primeResources({ |
| req: mockReq, |
| appConfig: mockAppConfig, |
| getFiles: mockGetFiles, |
| requestFileSet, |
| attachments, |
| tool_resources, |
| }); |
|
|
| expect(logger.error).toHaveBeenCalledWith('Error priming resources', error); |
| expect(result.attachments).toEqual(mockFiles); |
| expect(result.tool_resources).toEqual(tool_resources); |
| }); |
|
|
| it('should handle promise rejection in attachments', async () => { |
| const error = new Error('Attachment error'); |
| const attachments = Promise.reject(error); |
|
|
| |
| const result = await primeResources({ |
| req: mockReq, |
| appConfig: mockAppConfig, |
| getFiles: mockGetFiles, |
| requestFileSet, |
| attachments, |
| tool_resources: {}, |
| }); |
|
|
| |
| expect(logger.error).toHaveBeenCalledWith('Error priming resources', error); |
| expect(logger.error).toHaveBeenCalledWith( |
| 'Error resolving attachments in catch block', |
| error, |
| ); |
|
|
| |
| expect(result.attachments).toEqual([]); |
| expect(result.tool_resources).toEqual({}); |
| }); |
| }); |
|
|
| describe('tool_resources field deletion behavior', () => { |
| it('should not mutate the original tool_resources object', async () => { |
| const originalToolResources = { |
| [EToolResources.context]: { |
| file_ids: ['context-file-1'], |
| files: [ |
| { |
| user: 'user1', |
| file_id: 'context-file-1', |
| filename: 'original.txt', |
| filepath: '/uploads/original.txt', |
| object: 'file' as const, |
| type: 'text/plain', |
| bytes: 256, |
| embedded: false, |
| usage: 0, |
| }, |
| ], |
| }, |
| [EToolResources.ocr]: { |
| file_ids: ['ocr-file-1'], |
| }, |
| }; |
|
|
| |
| const originalCopy = JSON.parse(JSON.stringify(originalToolResources)); |
|
|
| const mockOcrFiles: TFile[] = [ |
| { |
| user: 'user1', |
| file_id: 'ocr-file-1', |
| filename: 'document.pdf', |
| filepath: '/uploads/document.pdf', |
| object: 'file', |
| type: 'application/pdf', |
| bytes: 1024, |
| embedded: true, |
| usage: 0, |
| }, |
| ]; |
|
|
| mockGetFiles.mockResolvedValue(mockOcrFiles); |
|
|
| const result = await primeResources({ |
| req: mockReq, |
| appConfig: mockAppConfig, |
| getFiles: mockGetFiles, |
| requestFileSet, |
| attachments: undefined, |
| tool_resources: originalToolResources, |
| }); |
|
|
| |
| expect(originalToolResources).toEqual(originalCopy); |
|
|
| |
| expect(result.tool_resources?.[EToolResources.ocr]).toBeUndefined(); |
| expect(result.tool_resources?.[EToolResources.context]).toBeUndefined(); |
| expect(result.tool_resources?.[EToolResources.file_search]).toBeDefined(); |
| }); |
|
|
| it('should delete ocr field after merging file_ids with context', async () => { |
| const mockOcrFiles: TFile[] = [ |
| { |
| user: 'user1', |
| file_id: 'ocr-file-1', |
| filename: 'document.pdf', |
| filepath: '/uploads/document.pdf', |
| object: 'file', |
| type: 'application/pdf', |
| bytes: 1024, |
| embedded: true, |
| usage: 0, |
| }, |
| ]; |
|
|
| mockGetFiles.mockResolvedValue(mockOcrFiles); |
|
|
| const tool_resources = { |
| [EToolResources.ocr]: { |
| file_ids: ['ocr-file-1'], |
| }, |
| [EToolResources.context]: { |
| file_ids: ['context-file-1'], |
| }, |
| }; |
|
|
| const result = await primeResources({ |
| req: mockReq, |
| appConfig: mockAppConfig, |
| getFiles: mockGetFiles, |
| requestFileSet, |
| attachments: undefined, |
| tool_resources, |
| }); |
|
|
| |
| expect(result.tool_resources?.[EToolResources.ocr]).toBeUndefined(); |
| |
| expect(result.tool_resources?.[EToolResources.context]).toBeUndefined(); |
| |
| expect(result.tool_resources?.[EToolResources.file_search]?.files).toHaveLength(1); |
| expect(result.tool_resources?.[EToolResources.file_search]?.files?.[0]?.file_id).toBe( |
| 'ocr-file-1', |
| ); |
|
|
| |
| expect(mockGetFiles).toHaveBeenCalledWith( |
| { file_id: { $in: ['context-file-1', 'ocr-file-1'] } }, |
| {}, |
| {}, |
| { userId: undefined, agentId: undefined }, |
| ); |
| }); |
|
|
| it('should delete context field when fetching and re-categorizing files', async () => { |
| const mockContextFiles: TFile[] = [ |
| { |
| user: 'user1', |
| file_id: 'context-file-1', |
| filename: 'script.py', |
| filepath: '/uploads/script.py', |
| object: 'file', |
| type: 'text/x-python', |
| bytes: 512, |
| embedded: false, |
| usage: 0, |
| metadata: { |
| fileIdentifier: 'python-script', |
| }, |
| }, |
| { |
| user: 'user1', |
| file_id: 'context-file-2', |
| filename: 'data.txt', |
| filepath: '/uploads/data.txt', |
| object: 'file', |
| type: 'text/plain', |
| bytes: 256, |
| embedded: true, |
| usage: 0, |
| }, |
| ]; |
|
|
| mockGetFiles.mockResolvedValue(mockContextFiles); |
|
|
| const tool_resources = { |
| [EToolResources.context]: { |
| file_ids: ['context-file-1', 'context-file-2'], |
| }, |
| }; |
|
|
| const result = await primeResources({ |
| req: mockReq, |
| appConfig: mockAppConfig, |
| getFiles: mockGetFiles, |
| requestFileSet, |
| attachments: undefined, |
| tool_resources, |
| }); |
|
|
| |
| expect(result.tool_resources?.[EToolResources.context]).toBeUndefined(); |
|
|
| |
| expect(result.tool_resources?.[EToolResources.execute_code]?.files).toHaveLength(1); |
| expect(result.tool_resources?.[EToolResources.execute_code]?.files?.[0]?.file_id).toBe( |
| 'context-file-1', |
| ); |
|
|
| expect(result.tool_resources?.[EToolResources.file_search]?.files).toHaveLength(1); |
| expect(result.tool_resources?.[EToolResources.file_search]?.files?.[0]?.file_id).toBe( |
| 'context-file-2', |
| ); |
| }); |
|
|
| it('should preserve context field when context capability is disabled', async () => { |
| |
| (mockAppConfig.endpoints![EModelEndpoint.agents] as TAgentsEndpoint).capabilities = []; |
|
|
| const tool_resources = { |
| [EToolResources.context]: { |
| file_ids: ['context-file-1'], |
| }, |
| }; |
|
|
| const result = await primeResources({ |
| req: mockReq, |
| appConfig: mockAppConfig, |
| getFiles: mockGetFiles, |
| requestFileSet, |
| attachments: undefined, |
| tool_resources, |
| }); |
|
|
| |
| expect(result.tool_resources?.[EToolResources.context]).toEqual({ |
| file_ids: ['context-file-1'], |
| }); |
|
|
| |
| expect(mockGetFiles).not.toHaveBeenCalled(); |
| }); |
|
|
| it('should still delete ocr field even when context capability is disabled', async () => { |
| |
| (mockAppConfig.endpoints![EModelEndpoint.agents] as TAgentsEndpoint).capabilities = []; |
|
|
| const tool_resources = { |
| [EToolResources.ocr]: { |
| file_ids: ['ocr-file-1'], |
| }, |
| [EToolResources.context]: { |
| file_ids: ['context-file-1'], |
| }, |
| }; |
|
|
| const result = await primeResources({ |
| req: mockReq, |
| appConfig: mockAppConfig, |
| getFiles: mockGetFiles, |
| requestFileSet, |
| attachments: undefined, |
| tool_resources, |
| }); |
|
|
| |
| expect(result.tool_resources?.[EToolResources.ocr]).toBeUndefined(); |
|
|
| |
| expect(result.tool_resources?.[EToolResources.context]).toEqual({ |
| file_ids: ['context-file-1', 'ocr-file-1'], |
| }); |
|
|
| |
| expect(mockGetFiles).not.toHaveBeenCalled(); |
| }); |
| }); |
|
|
| describe('edge cases', () => { |
| it('should handle missing appConfig agents endpoint gracefully', async () => { |
| const reqWithoutLocals = {} as ServerRequest & { user?: IUser }; |
| const emptyAppConfig = {} as AppConfig; |
|
|
| const result = await primeResources({ |
| req: reqWithoutLocals, |
| appConfig: emptyAppConfig, |
| getFiles: mockGetFiles, |
| requestFileSet, |
| attachments: undefined, |
| tool_resources: { |
| [EToolResources.context]: { |
| file_ids: ['ocr-file-1'], |
| }, |
| }, |
| }); |
|
|
| expect(mockGetFiles).not.toHaveBeenCalled(); |
| |
| |
| expect(result.attachments).toBeUndefined(); |
| }); |
|
|
| it('should handle undefined tool_resources', async () => { |
| const result = await primeResources({ |
| req: mockReq, |
| appConfig: mockAppConfig, |
| getFiles: mockGetFiles, |
| requestFileSet, |
| attachments: undefined, |
| tool_resources: undefined, |
| }); |
|
|
| expect(result.tool_resources).toEqual({}); |
| expect(result.attachments).toBeUndefined(); |
| }); |
|
|
| it('should handle empty requestFileSet', async () => { |
| const mockFiles: TFile[] = [ |
| { |
| user: 'user1', |
| file_id: 'file1', |
| filename: 'image.png', |
| filepath: '/uploads/image.png', |
| object: 'file', |
| type: 'image/png', |
| bytes: 2048, |
| embedded: false, |
| usage: 0, |
| height: 800, |
| width: 600, |
| }, |
| ]; |
|
|
| const attachments = Promise.resolve(mockFiles); |
| const emptyRequestFileSet = new Set<string>(); |
|
|
| const result = await primeResources({ |
| req: mockReq, |
| appConfig: mockAppConfig, |
| getFiles: mockGetFiles, |
| requestFileSet: emptyRequestFileSet, |
| attachments, |
| tool_resources: {}, |
| }); |
|
|
| expect(result.attachments).toEqual(mockFiles); |
| expect(result.tool_resources?.[EToolResources.image_edit]).toBeUndefined(); |
| }); |
| }); |
| }); |
|
|