mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-01-12 21:48:51 +01:00
🌉 fix: Add Proxy Support to Gemini Image Gen Tool (#11302)
Some checks are pending
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Waiting to run
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Waiting to run
Docker Dev Images Build / build (Dockerfile, librechat-dev, node) (push) Waiting to run
Docker Dev Images Build / build (Dockerfile.multi, librechat-dev-api, api-build) (push) Waiting to run
Sync Locize Translations & Create Translation PR / Sync Translation Keys with Locize (push) Waiting to run
Sync Locize Translations & Create Translation PR / Create Translation PR on Version Published (push) Blocked by required conditions
Some checks are pending
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Waiting to run
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Waiting to run
Docker Dev Images Build / build (Dockerfile, librechat-dev, node) (push) Waiting to run
Docker Dev Images Build / build (Dockerfile.multi, librechat-dev-api, api-build) (push) Waiting to run
Sync Locize Translations & Create Translation PR / Sync Translation Keys with Locize (push) Waiting to run
Sync Locize Translations & Create Translation PR / Create Translation PR on Version Published (push) Blocked by required conditions
* ✨ feat: Add proxy support for Google APIs in GeminiImageGen
- Implemented a proxy wrapper for globalThis.fetch to route requests to googleapis.com through a specified proxy.
- Added tests to verify the proxy configuration behavior, ensuring correct dispatcher application for Google API calls and preserving existing options.
Co-authored-by: [Your Name] <your.email@example.com>
* chore: remove comment
---------
Co-authored-by: [Your Name] <your.email@example.com>
Co-authored-by: Danny Avila <danacordially@gmail.com>
This commit is contained in:
parent
cdffdd2926
commit
fc6f127b21
2 changed files with 144 additions and 0 deletions
|
|
@ -2,6 +2,7 @@ const fs = require('fs');
|
|||
const path = require('path');
|
||||
const sharp = require('sharp');
|
||||
const { v4 } = require('uuid');
|
||||
const { ProxyAgent } = require('undici');
|
||||
const { GoogleGenAI } = require('@google/genai');
|
||||
const { tool } = require('@langchain/core/tools');
|
||||
const { logger } = require('@librechat/data-schemas');
|
||||
|
|
@ -21,6 +22,24 @@ const { getStrategyFunctions } = require('~/server/services/Files/strategies');
|
|||
const { spendTokens } = require('~/models/spendTokens');
|
||||
const { getFiles } = require('~/models/File');
|
||||
|
||||
/**
|
||||
* Configure proxy support for Google APIs
|
||||
* This wraps globalThis.fetch to add a proxy dispatcher only for googleapis.com URLs
|
||||
* This is necessary because @google/genai SDK doesn't support custom fetch or httpOptions.dispatcher
|
||||
*/
|
||||
if (process.env.PROXY) {
|
||||
const originalFetch = globalThis.fetch;
|
||||
const proxyAgent = new ProxyAgent(process.env.PROXY);
|
||||
|
||||
globalThis.fetch = function (url, options = {}) {
|
||||
const urlString = url.toString();
|
||||
if (urlString.includes('googleapis.com')) {
|
||||
options = { ...options, dispatcher: proxyAgent };
|
||||
}
|
||||
return originalFetch.call(this, url, options);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default service key file path (consistent with main Google endpoint)
|
||||
* @returns {string} - The default path to the service key file
|
||||
|
|
|
|||
|
|
@ -0,0 +1,125 @@
|
|||
const { ProxyAgent } = require('undici');
|
||||
|
||||
/**
|
||||
* These tests verify the proxy wrapper behavior for GeminiImageGen.
|
||||
* Instead of loading the full module (which has many dependencies),
|
||||
* we directly test the wrapper logic that would be applied.
|
||||
*/
|
||||
describe('GeminiImageGen Proxy Configuration', () => {
|
||||
let originalEnv;
|
||||
let originalFetch;
|
||||
|
||||
beforeAll(() => {
|
||||
originalEnv = { ...process.env };
|
||||
originalFetch = globalThis.fetch;
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
process.env = { ...originalEnv };
|
||||
globalThis.fetch = originalFetch;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
process.env = originalEnv;
|
||||
globalThis.fetch = originalFetch;
|
||||
});
|
||||
|
||||
/**
|
||||
* Simulates the proxy wrapper that GeminiImageGen applies at module load.
|
||||
* This is the same logic from GeminiImageGen.js lines 30-42.
|
||||
*/
|
||||
function applyProxyWrapper() {
|
||||
if (process.env.PROXY) {
|
||||
const _originalFetch = globalThis.fetch;
|
||||
const proxyAgent = new ProxyAgent(process.env.PROXY);
|
||||
|
||||
globalThis.fetch = function (url, options = {}) {
|
||||
const urlString = url.toString();
|
||||
if (urlString.includes('googleapis.com')) {
|
||||
options = { ...options, dispatcher: proxyAgent };
|
||||
}
|
||||
return _originalFetch.call(this, url, options);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
it('should wrap globalThis.fetch when PROXY env is set', () => {
|
||||
process.env.PROXY = 'http://proxy.example.com:8080';
|
||||
|
||||
const fetchBeforeWrap = globalThis.fetch;
|
||||
|
||||
applyProxyWrapper();
|
||||
|
||||
expect(globalThis.fetch).not.toBe(fetchBeforeWrap);
|
||||
});
|
||||
|
||||
it('should not wrap globalThis.fetch when PROXY env is not set', () => {
|
||||
delete process.env.PROXY;
|
||||
|
||||
const fetchBeforeWrap = globalThis.fetch;
|
||||
|
||||
applyProxyWrapper();
|
||||
|
||||
expect(globalThis.fetch).toBe(fetchBeforeWrap);
|
||||
});
|
||||
|
||||
it('should add dispatcher to googleapis.com URLs', async () => {
|
||||
process.env.PROXY = 'http://proxy.example.com:8080';
|
||||
|
||||
let capturedOptions = null;
|
||||
const mockFetch = jest.fn((url, options) => {
|
||||
capturedOptions = options;
|
||||
return Promise.resolve({ ok: true });
|
||||
});
|
||||
globalThis.fetch = mockFetch;
|
||||
|
||||
applyProxyWrapper();
|
||||
|
||||
await globalThis.fetch('https://generativelanguage.googleapis.com/v1/models', {});
|
||||
|
||||
expect(capturedOptions).toBeDefined();
|
||||
expect(capturedOptions.dispatcher).toBeInstanceOf(ProxyAgent);
|
||||
});
|
||||
|
||||
it('should not add dispatcher to non-googleapis.com URLs', async () => {
|
||||
process.env.PROXY = 'http://proxy.example.com:8080';
|
||||
|
||||
let capturedOptions = null;
|
||||
const mockFetch = jest.fn((url, options) => {
|
||||
capturedOptions = options;
|
||||
return Promise.resolve({ ok: true });
|
||||
});
|
||||
globalThis.fetch = mockFetch;
|
||||
|
||||
applyProxyWrapper();
|
||||
|
||||
await globalThis.fetch('https://api.openai.com/v1/images', {});
|
||||
|
||||
expect(capturedOptions).toBeDefined();
|
||||
expect(capturedOptions.dispatcher).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should preserve existing options when adding dispatcher', async () => {
|
||||
process.env.PROXY = 'http://proxy.example.com:8080';
|
||||
|
||||
let capturedOptions = null;
|
||||
const mockFetch = jest.fn((url, options) => {
|
||||
capturedOptions = options;
|
||||
return Promise.resolve({ ok: true });
|
||||
});
|
||||
globalThis.fetch = mockFetch;
|
||||
|
||||
applyProxyWrapper();
|
||||
|
||||
const customHeaders = { 'X-Custom-Header': 'test' };
|
||||
await globalThis.fetch('https://aiplatform.googleapis.com/v1/models', {
|
||||
headers: customHeaders,
|
||||
method: 'POST',
|
||||
});
|
||||
|
||||
expect(capturedOptions).toBeDefined();
|
||||
expect(capturedOptions.dispatcher).toBeInstanceOf(ProxyAgent);
|
||||
expect(capturedOptions.headers).toEqual(customHeaders);
|
||||
expect(capturedOptions.method).toBe('POST');
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue