mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-20 02:10:15 +01:00
💻 feat: Add Proxy Config for Mistral OCR API (#9629)
* 💻 feat: Add proxy configuration support for Mistral OCR API requests
* refactor: Implement proxy support for Mistral API requests using HttpsProxyAgent
This commit is contained in:
parent
2ce8f1f686
commit
5bfb06b417
2 changed files with 480 additions and 37 deletions
|
|
@ -10,6 +10,9 @@ jest.mock('form-data', () => {
|
||||||
getLength: jest.fn().mockReturnValue(100),
|
getLength: jest.fn().mockReturnValue(100),
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
jest.mock('https-proxy-agent', () => ({
|
||||||
|
HttpsProxyAgent: jest.fn().mockImplementation((url) => ({ proxyUrl: url })),
|
||||||
|
}));
|
||||||
jest.mock('axios', () => {
|
jest.mock('axios', () => {
|
||||||
const mockAxiosInstance = {
|
const mockAxiosInstance = {
|
||||||
get: jest.fn().mockResolvedValue({ data: {} }),
|
get: jest.fn().mockResolvedValue({ data: {} }),
|
||||||
|
|
@ -44,6 +47,7 @@ jest.mock('~/utils/axios', () => ({
|
||||||
|
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
import { HttpsProxyAgent } from 'https-proxy-agent';
|
||||||
import type { Readable } from 'stream';
|
import type { Readable } from 'stream';
|
||||||
import type {
|
import type {
|
||||||
MistralFileUploadResponse,
|
MistralFileUploadResponse,
|
||||||
|
|
@ -1182,6 +1186,8 @@ describe('MistralOCR Service', () => {
|
||||||
|
|
||||||
describe('Mixed env var and hardcoded configuration', () => {
|
describe('Mixed env var and hardcoded configuration', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
// Clean up any PROXY env var from previous tests
|
||||||
|
delete process.env.PROXY;
|
||||||
const mockReadStream: MockReadStream = {
|
const mockReadStream: MockReadStream = {
|
||||||
on: jest.fn().mockImplementation(function (
|
on: jest.fn().mockImplementation(function (
|
||||||
this: MockReadStream,
|
this: MockReadStream,
|
||||||
|
|
@ -1708,9 +1714,403 @@ describe('MistralOCR Service', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Proxy Configuration', () => {
|
||||||
|
const originalProxy = process.env.PROXY;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// Reset the HttpsProxyAgent mock to its default implementation
|
||||||
|
(HttpsProxyAgent as unknown as jest.Mock).mockImplementation((url) => ({ proxyUrl: url }));
|
||||||
|
// Clear any previous axios mock calls
|
||||||
|
mockAxios.post!.mockClear();
|
||||||
|
mockAxios.get!.mockClear();
|
||||||
|
mockAxios.delete!.mockClear();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
if (originalProxy) {
|
||||||
|
process.env.PROXY = originalProxy;
|
||||||
|
} else {
|
||||||
|
delete process.env.PROXY;
|
||||||
|
}
|
||||||
|
// Clear mocks after each test to prevent leaking
|
||||||
|
mockAxios.post!.mockClear();
|
||||||
|
mockAxios.get!.mockClear();
|
||||||
|
mockAxios.delete!.mockClear();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('uploadDocumentToMistral with proxy', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
const mockReadStream: MockReadStream = {
|
||||||
|
on: jest.fn().mockImplementation(function (
|
||||||
|
this: MockReadStream,
|
||||||
|
event: string,
|
||||||
|
handler: () => void,
|
||||||
|
) {
|
||||||
|
if (event === 'end') {
|
||||||
|
handler();
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}),
|
||||||
|
pipe: jest.fn().mockImplementation(function (this: MockReadStream) {
|
||||||
|
return this;
|
||||||
|
}),
|
||||||
|
pause: jest.fn(),
|
||||||
|
resume: jest.fn(),
|
||||||
|
emit: jest.fn(),
|
||||||
|
once: jest.fn(),
|
||||||
|
destroy: jest.fn(),
|
||||||
|
path: '/path/to/test.pdf',
|
||||||
|
fd: 1,
|
||||||
|
flags: 'r',
|
||||||
|
mode: 0o666,
|
||||||
|
autoClose: true,
|
||||||
|
bytesRead: 0,
|
||||||
|
closed: false,
|
||||||
|
pending: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
(jest.mocked(fs).createReadStream as jest.Mock).mockReturnValue(mockReadStream);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use proxy configuration when PROXY env var is set', async () => {
|
||||||
|
process.env.PROXY = 'http://proxy.example.com:8080';
|
||||||
|
|
||||||
|
const mockResponse: { data: MistralFileUploadResponse } = {
|
||||||
|
data: {
|
||||||
|
id: 'file-proxy-123',
|
||||||
|
object: 'file',
|
||||||
|
bytes: 1024,
|
||||||
|
created_at: Date.now(),
|
||||||
|
filename: 'test.pdf',
|
||||||
|
purpose: 'ocr',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
mockAxios.post!.mockResolvedValueOnce(mockResponse);
|
||||||
|
|
||||||
|
await uploadDocumentToMistral({
|
||||||
|
filePath: '/path/to/test.pdf',
|
||||||
|
fileName: 'test.pdf',
|
||||||
|
apiKey: 'test-api-key',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mockAxios.post).toHaveBeenCalledWith(
|
||||||
|
'https://api.mistral.ai/v1/files',
|
||||||
|
expect.anything(),
|
||||||
|
expect.objectContaining({
|
||||||
|
httpsAgent: expect.objectContaining({
|
||||||
|
proxyUrl: 'http://proxy.example.com:8080',
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle proxy URL with authentication', async () => {
|
||||||
|
process.env.PROXY = 'http://user:pass@proxy.example.com:8080';
|
||||||
|
|
||||||
|
const mockResponse: { data: MistralFileUploadResponse } = {
|
||||||
|
data: {
|
||||||
|
id: 'file-proxy-auth-123',
|
||||||
|
object: 'file',
|
||||||
|
bytes: 1024,
|
||||||
|
created_at: Date.now(),
|
||||||
|
filename: 'test.pdf',
|
||||||
|
purpose: 'ocr',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
mockAxios.post!.mockResolvedValueOnce(mockResponse);
|
||||||
|
|
||||||
|
await uploadDocumentToMistral({
|
||||||
|
filePath: '/path/to/test.pdf',
|
||||||
|
fileName: 'test.pdf',
|
||||||
|
apiKey: 'test-api-key',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mockAxios.post).toHaveBeenCalledWith(
|
||||||
|
'https://api.mistral.ai/v1/files',
|
||||||
|
expect.anything(),
|
||||||
|
expect.objectContaining({
|
||||||
|
httpsAgent: expect.objectContaining({
|
||||||
|
proxyUrl: 'http://user:pass@proxy.example.com:8080',
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle IPv6 proxy addresses', async () => {
|
||||||
|
process.env.PROXY = 'http://[::1]:8080';
|
||||||
|
|
||||||
|
const mockResponse: { data: MistralFileUploadResponse } = {
|
||||||
|
data: {
|
||||||
|
id: 'file-proxy-ipv6-123',
|
||||||
|
object: 'file',
|
||||||
|
bytes: 1024,
|
||||||
|
created_at: Date.now(),
|
||||||
|
filename: 'test.pdf',
|
||||||
|
purpose: 'ocr',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
mockAxios.post!.mockResolvedValueOnce(mockResponse);
|
||||||
|
|
||||||
|
await uploadDocumentToMistral({
|
||||||
|
filePath: '/path/to/test.pdf',
|
||||||
|
fileName: 'test.pdf',
|
||||||
|
apiKey: 'test-api-key',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mockAxios.post).toHaveBeenCalledWith(
|
||||||
|
'https://api.mistral.ai/v1/files',
|
||||||
|
expect.anything(),
|
||||||
|
expect.objectContaining({
|
||||||
|
httpsAgent: expect.objectContaining({
|
||||||
|
proxyUrl: 'http://[::1]:8080',
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not use proxy when PROXY env var is not set', async () => {
|
||||||
|
delete process.env.PROXY;
|
||||||
|
|
||||||
|
const mockResponse: { data: MistralFileUploadResponse } = {
|
||||||
|
data: {
|
||||||
|
id: 'file-no-proxy-123',
|
||||||
|
object: 'file',
|
||||||
|
bytes: 1024,
|
||||||
|
created_at: Date.now(),
|
||||||
|
filename: 'test.pdf',
|
||||||
|
purpose: 'ocr',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
mockAxios.post!.mockResolvedValueOnce(mockResponse);
|
||||||
|
|
||||||
|
await uploadDocumentToMistral({
|
||||||
|
filePath: '/path/to/test.pdf',
|
||||||
|
fileName: 'test.pdf',
|
||||||
|
apiKey: 'test-api-key',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mockAxios.post).toHaveBeenCalledWith(
|
||||||
|
'https://api.mistral.ai/v1/files',
|
||||||
|
expect.anything(),
|
||||||
|
expect.not.objectContaining({
|
||||||
|
httpsAgent: expect.anything(),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('performOCR with proxy', () => {
|
||||||
|
it('should use proxy configuration when PROXY env var is set', async () => {
|
||||||
|
process.env.PROXY = 'http://proxy.example.com:3128';
|
||||||
|
|
||||||
|
const mockResponse: { data: OCRResult } = {
|
||||||
|
data: {
|
||||||
|
model: 'mistral-ocr-latest',
|
||||||
|
pages: [
|
||||||
|
{
|
||||||
|
index: 0,
|
||||||
|
markdown: 'Proxy test content',
|
||||||
|
images: [],
|
||||||
|
dimensions: { dpi: 300, height: 1100, width: 850 },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
document_annotation: '',
|
||||||
|
usage_info: {
|
||||||
|
pages_processed: 1,
|
||||||
|
doc_size_bytes: 1024,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
mockAxios.post!.mockResolvedValueOnce(mockResponse);
|
||||||
|
|
||||||
|
await performOCR({
|
||||||
|
apiKey: 'test-api-key',
|
||||||
|
url: 'https://document-url.com',
|
||||||
|
model: 'mistral-ocr-latest',
|
||||||
|
documentType: 'document_url',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mockAxios.post).toHaveBeenCalledWith(
|
||||||
|
'https://api.mistral.ai/v1/ocr',
|
||||||
|
expect.anything(),
|
||||||
|
expect.objectContaining({
|
||||||
|
httpsAgent: expect.objectContaining({
|
||||||
|
proxyUrl: 'http://proxy.example.com:3128',
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle malformed proxy URLs gracefully', async () => {
|
||||||
|
(HttpsProxyAgent as unknown as jest.Mock).mockImplementationOnce(() => {
|
||||||
|
throw new Error('Invalid URL');
|
||||||
|
});
|
||||||
|
process.env.PROXY = 'not-a-valid-url';
|
||||||
|
|
||||||
|
const mockResponse: { data: OCRResult } = {
|
||||||
|
data: {
|
||||||
|
model: 'mistral-ocr-latest',
|
||||||
|
pages: [
|
||||||
|
{
|
||||||
|
index: 0,
|
||||||
|
markdown: 'Test content',
|
||||||
|
images: [],
|
||||||
|
dimensions: { dpi: 300, height: 1100, width: 850 },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
document_annotation: '',
|
||||||
|
usage_info: {
|
||||||
|
pages_processed: 1,
|
||||||
|
doc_size_bytes: 1024,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
mockAxios.post!.mockResolvedValueOnce(mockResponse);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
performOCR({
|
||||||
|
apiKey: 'test-api-key',
|
||||||
|
url: 'https://document-url.com',
|
||||||
|
}),
|
||||||
|
).rejects.toThrow('Invalid URL');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Azure Mistral OCR with proxy', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
(jest.mocked(fs).readFileSync as jest.Mock).mockReturnValue(
|
||||||
|
Buffer.from('mock-file-content'),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use proxy for Azure Mistral OCR requests', async () => {
|
||||||
|
process.env.PROXY = 'http://proxy.example.com:8080';
|
||||||
|
|
||||||
|
mockLoadAuthValues.mockResolvedValue({
|
||||||
|
OCR_API_KEY: 'azure-api-key',
|
||||||
|
OCR_BASEURL: 'https://azure.mistral.ai/v1',
|
||||||
|
});
|
||||||
|
|
||||||
|
mockAxios.post!.mockResolvedValueOnce({
|
||||||
|
data: {
|
||||||
|
model: 'mistral-ocr-latest',
|
||||||
|
pages: [
|
||||||
|
{
|
||||||
|
index: 0,
|
||||||
|
markdown: 'Azure OCR with proxy',
|
||||||
|
images: [],
|
||||||
|
dimensions: { dpi: 300, height: 1100, width: 850 },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
document_annotation: '',
|
||||||
|
usage_info: {
|
||||||
|
pages_processed: 1,
|
||||||
|
doc_size_bytes: 1024,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const req = {
|
||||||
|
user: { id: 'user123' },
|
||||||
|
config: {
|
||||||
|
ocr: {
|
||||||
|
apiKey: '${OCR_API_KEY}',
|
||||||
|
baseURL: '${OCR_BASEURL}',
|
||||||
|
mistralModel: 'mistral-ocr-latest',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as unknown as ServerRequest;
|
||||||
|
|
||||||
|
const file = {
|
||||||
|
path: '/tmp/upload/azure-file.pdf',
|
||||||
|
originalname: 'azure-document.pdf',
|
||||||
|
mimetype: 'application/pdf',
|
||||||
|
} as Express.Multer.File;
|
||||||
|
|
||||||
|
await uploadAzureMistralOCR({
|
||||||
|
req,
|
||||||
|
file,
|
||||||
|
loadAuthValues: mockLoadAuthValues,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mockAxios.post).toHaveBeenCalledWith(
|
||||||
|
'https://azure.mistral.ai/v1/ocr',
|
||||||
|
expect.anything(),
|
||||||
|
expect.objectContaining({
|
||||||
|
httpsAgent: expect.objectContaining({
|
||||||
|
proxyUrl: 'http://proxy.example.com:8080',
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getSignedUrl with proxy', () => {
|
||||||
|
it('should use proxy configuration when PROXY env var is set', async () => {
|
||||||
|
process.env.PROXY = 'https://secure-proxy.example.com:443';
|
||||||
|
|
||||||
|
const mockResponse: { data: MistralSignedUrlResponse } = {
|
||||||
|
data: {
|
||||||
|
url: 'https://signed-url.com',
|
||||||
|
expires_at: Date.now() + 86400000,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
mockAxios.get!.mockResolvedValueOnce(mockResponse);
|
||||||
|
|
||||||
|
await getSignedUrl({
|
||||||
|
fileId: 'file-123',
|
||||||
|
apiKey: 'test-api-key',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mockAxios.get).toHaveBeenCalledWith(
|
||||||
|
'https://api.mistral.ai/v1/files/file-123/url?expiry=24',
|
||||||
|
expect.objectContaining({
|
||||||
|
httpsAgent: expect.objectContaining({
|
||||||
|
proxyUrl: 'https://secure-proxy.example.com:443',
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('deleteMistralFile with proxy', () => {
|
||||||
|
it('should use proxy configuration when PROXY env var is set', async () => {
|
||||||
|
process.env.PROXY = 'socks5://proxy.example.com:1080';
|
||||||
|
|
||||||
|
mockAxios.delete!.mockResolvedValueOnce({ data: {} });
|
||||||
|
|
||||||
|
await deleteMistralFile({
|
||||||
|
fileId: 'file-123',
|
||||||
|
apiKey: 'test-api-key',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mockAxios.delete).toHaveBeenCalledWith(
|
||||||
|
'https://api.mistral.ai/v1/files/file-123',
|
||||||
|
expect.objectContaining({
|
||||||
|
httpsAgent: expect.objectContaining({
|
||||||
|
proxyUrl: 'socks5://proxy.example.com:1080',
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('uploadAzureMistralOCR', () => {
|
describe('uploadAzureMistralOCR', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
(jest.mocked(fs).readFileSync as jest.Mock).mockReturnValue(Buffer.from('mock-file-content'));
|
(jest.mocked(fs).readFileSync as jest.Mock).mockReturnValue(Buffer.from('mock-file-content'));
|
||||||
|
// Reset the HttpsProxyAgent mock to its default implementation for Azure tests
|
||||||
|
(HttpsProxyAgent as unknown as jest.Mock).mockImplementation((url) => ({ proxyUrl: url }));
|
||||||
|
// Clean up any PROXY env var from previous tests
|
||||||
|
delete process.env.PROXY;
|
||||||
|
// Reset axios mocks completely to clear any queued responses
|
||||||
|
mockAxios.post!.mockReset();
|
||||||
|
mockAxios.get!.mockReset();
|
||||||
|
mockAxios.delete!.mockReset();
|
||||||
|
// Re-establish default resolved values
|
||||||
|
mockAxios.post!.mockResolvedValue({ data: {} });
|
||||||
|
mockAxios.get!.mockResolvedValue({ data: {} });
|
||||||
|
mockAxios.delete!.mockResolvedValue({ data: {} });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should process OCR using Azure Mistral with base64 encoding', async () => {
|
it('should process OCR using Azure Mistral with base64 encoding', async () => {
|
||||||
|
|
@ -1796,6 +2196,11 @@ describe('MistralOCR Service', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Mixed env var and hardcoded configuration', () => {
|
describe('Mixed env var and hardcoded configuration', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// Clean up any PROXY env var from previous tests
|
||||||
|
delete process.env.PROXY;
|
||||||
|
});
|
||||||
|
|
||||||
it('should preserve hardcoded baseURL when only apiKey is an env var', async () => {
|
it('should preserve hardcoded baseURL when only apiKey is an env var', async () => {
|
||||||
// This test demonstrates the current bug
|
// This test demonstrates the current bug
|
||||||
mockLoadAuthValues.mockResolvedValue({
|
mockLoadAuthValues.mockResolvedValue({
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import FormData from 'form-data';
|
import FormData from 'form-data';
|
||||||
import { logger } from '@librechat/data-schemas';
|
import { logger } from '@librechat/data-schemas';
|
||||||
|
import { HttpsProxyAgent } from 'https-proxy-agent';
|
||||||
import {
|
import {
|
||||||
FileSources,
|
FileSources,
|
||||||
envVarRegex,
|
envVarRegex,
|
||||||
|
|
@ -9,7 +10,7 @@ import {
|
||||||
extractVariableName,
|
extractVariableName,
|
||||||
} from 'librechat-data-provider';
|
} from 'librechat-data-provider';
|
||||||
import type { TCustomConfig } from 'librechat-data-provider';
|
import type { TCustomConfig } from 'librechat-data-provider';
|
||||||
import type { AxiosError } from 'axios';
|
import type { AxiosError, AxiosRequestConfig } from 'axios';
|
||||||
import type {
|
import type {
|
||||||
MistralFileUploadResponse,
|
MistralFileUploadResponse,
|
||||||
MistralSignedUrlResponse,
|
MistralSignedUrlResponse,
|
||||||
|
|
@ -77,15 +78,21 @@ export async function uploadDocumentToMistral({
|
||||||
const fileStream = fs.createReadStream(filePath);
|
const fileStream = fs.createReadStream(filePath);
|
||||||
form.append('file', fileStream, { filename: actualFileName });
|
form.append('file', fileStream, { filename: actualFileName });
|
||||||
|
|
||||||
|
const config: AxiosRequestConfig = {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${apiKey}`,
|
||||||
|
...form.getHeaders(),
|
||||||
|
},
|
||||||
|
maxBodyLength: Infinity,
|
||||||
|
maxContentLength: Infinity,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (process.env.PROXY) {
|
||||||
|
config.httpsAgent = new HttpsProxyAgent(process.env.PROXY);
|
||||||
|
}
|
||||||
|
|
||||||
return axios
|
return axios
|
||||||
.post(`${baseURL}/files`, form, {
|
.post(`${baseURL}/files`, form, config)
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${apiKey}`,
|
|
||||||
...form.getHeaders(),
|
|
||||||
},
|
|
||||||
maxBodyLength: Infinity,
|
|
||||||
maxContentLength: Infinity,
|
|
||||||
})
|
|
||||||
.then((res) => res.data)
|
.then((res) => res.data)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
throw error;
|
throw error;
|
||||||
|
|
@ -103,12 +110,18 @@ export async function getSignedUrl({
|
||||||
expiry?: number;
|
expiry?: number;
|
||||||
baseURL?: string;
|
baseURL?: string;
|
||||||
}): Promise<MistralSignedUrlResponse> {
|
}): Promise<MistralSignedUrlResponse> {
|
||||||
|
const config: AxiosRequestConfig = {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${apiKey}`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (process.env.PROXY) {
|
||||||
|
config.httpsAgent = new HttpsProxyAgent(process.env.PROXY);
|
||||||
|
}
|
||||||
|
|
||||||
return axios
|
return axios
|
||||||
.get(`${baseURL}/files/${fileId}/url?expiry=${expiry}`, {
|
.get(`${baseURL}/files/${fileId}/url?expiry=${expiry}`, config)
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${apiKey}`,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then((res) => res.data)
|
.then((res) => res.data)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
logger.error('Error fetching signed URL:', error.message);
|
logger.error('Error fetching signed URL:', error.message);
|
||||||
|
|
@ -139,6 +152,18 @@ export async function performOCR({
|
||||||
documentType?: 'document_url' | 'image_url';
|
documentType?: 'document_url' | 'image_url';
|
||||||
}): Promise<OCRResult> {
|
}): Promise<OCRResult> {
|
||||||
const documentKey = documentType === 'image_url' ? 'image_url' : 'document_url';
|
const documentKey = documentType === 'image_url' ? 'image_url' : 'document_url';
|
||||||
|
|
||||||
|
const config: AxiosRequestConfig = {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${apiKey}`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (process.env.PROXY) {
|
||||||
|
config.httpsAgent = new HttpsProxyAgent(process.env.PROXY);
|
||||||
|
}
|
||||||
|
|
||||||
return axios
|
return axios
|
||||||
.post(
|
.post(
|
||||||
`${baseURL}/ocr`,
|
`${baseURL}/ocr`,
|
||||||
|
|
@ -151,12 +176,7 @@ export async function performOCR({
|
||||||
[documentKey]: url,
|
[documentKey]: url,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
config,
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
Authorization: `Bearer ${apiKey}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
.then((res) => res.data)
|
.then((res) => res.data)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
|
@ -182,12 +202,18 @@ export async function deleteMistralFile({
|
||||||
apiKey: string;
|
apiKey: string;
|
||||||
baseURL?: string;
|
baseURL?: string;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
|
const config: AxiosRequestConfig = {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${apiKey}`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (process.env.PROXY) {
|
||||||
|
config.httpsAgent = new HttpsProxyAgent(process.env.PROXY);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await axios.delete(`${baseURL}/files/${fileId}`, {
|
const result = await axios.delete(`${baseURL}/files/${fileId}`, config);
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${apiKey}`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
logger.debug(`Mistral file ${fileId} deleted successfully:`, result.data);
|
logger.debug(`Mistral file ${fileId} deleted successfully:`, result.data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`Error deleting Mistral file ${fileId}:`, error);
|
logger.error(`Error deleting Mistral file ${fileId}:`, error);
|
||||||
|
|
@ -543,17 +569,23 @@ async function createJWT(serviceKey: GoogleServiceAccount): Promise<string> {
|
||||||
* Exchanges JWT for access token
|
* Exchanges JWT for access token
|
||||||
*/
|
*/
|
||||||
async function exchangeJWTForAccessToken(jwt: string): Promise<string> {
|
async function exchangeJWTForAccessToken(jwt: string): Promise<string> {
|
||||||
|
const config: AxiosRequestConfig = {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (process.env.PROXY) {
|
||||||
|
config.httpsAgent = new HttpsProxyAgent(process.env.PROXY);
|
||||||
|
}
|
||||||
|
|
||||||
const response = await axios.post(
|
const response = await axios.post(
|
||||||
'https://oauth2.googleapis.com/token',
|
'https://oauth2.googleapis.com/token',
|
||||||
new URLSearchParams({
|
new URLSearchParams({
|
||||||
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
|
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
|
||||||
assertion: jwt,
|
assertion: jwt,
|
||||||
}),
|
}),
|
||||||
{
|
config,
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!response.data?.access_token) {
|
if (!response.data?.access_token) {
|
||||||
|
|
@ -608,14 +640,20 @@ async function performGoogleVertexOCR({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const config: AxiosRequestConfig = {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
Accept: 'application/json',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (process.env.PROXY) {
|
||||||
|
config.httpsAgent = new HttpsProxyAgent(process.env.PROXY);
|
||||||
|
}
|
||||||
|
|
||||||
return axios
|
return axios
|
||||||
.post(baseURL, requestBody, {
|
.post(baseURL, requestBody, config)
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
Authorization: `Bearer ${accessToken}`,
|
|
||||||
Accept: 'application/json',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
logger.debug('Google Vertex AI response received');
|
logger.debug('Google Vertex AI response received');
|
||||||
return res.data;
|
return res.data;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue