mirror of
https://github.com/n8n-io/n8n.git
synced 2026-10-17 13:39:44 +02:00
fix(core): Pair proxied AI fetches with their own undici (no-changelog) (#36318)
This commit is contained in:
parent
b671cf4276
commit
a0acb37ed2
|
|
@ -1,4 +1,4 @@
|
|||
import { Agent, ProxyAgent } from 'undici';
|
||||
import { Agent, ProxyAgent, fetch as undiciFetch } from 'undici';
|
||||
import type { MockedFunction } from 'vitest';
|
||||
|
||||
import { getNodeProxyAgent, getProxyAgent, proxyFetch } from 'src/utils/http-proxy-agent';
|
||||
|
|
@ -11,11 +11,9 @@ vi.mock('undici', () => ({
|
|||
ProxyAgent: vi.fn(function (options) {
|
||||
return { type: 'ProxyAgent', options };
|
||||
}),
|
||||
fetch: vi.fn(),
|
||||
}));
|
||||
|
||||
// Mock global fetch
|
||||
global.fetch = vi.fn();
|
||||
|
||||
describe('getProxyAgent', () => {
|
||||
// Store original environment variables
|
||||
const originalEnv = { ...process.env };
|
||||
|
|
@ -308,7 +306,7 @@ describe('getProxyAgent', () => {
|
|||
describe('proxyFetch', () => {
|
||||
// Store original environment variables
|
||||
const originalEnv = { ...process.env };
|
||||
const mockFetch = global.fetch as MockedFunction<typeof fetch>;
|
||||
const mockFetch = undiciFetch as unknown as MockedFunction<typeof fetch>;
|
||||
|
||||
// Reset environment variables and mocks before each test
|
||||
beforeEach(() => {
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import { createHttpsProxyAgent, resolveProxyUrl } from '@n8n/backend-network/pro
|
|||
import type { AgentOptions } from 'node:https';
|
||||
import type { LookupFunction } from 'node:net';
|
||||
/* eslint-disable n8n-local-rules/no-uncentralized-http -- langchain consumers pin undici v6, incompatible with backend-network's v7 dispatchers; see block comment below */
|
||||
import { Agent, ProxyAgent } from 'undici';
|
||||
import { Agent, ProxyAgent, fetch as undiciFetch } from 'undici';
|
||||
|
||||
/**
|
||||
* Options for configuring HTTP agent timeouts.
|
||||
|
|
@ -113,11 +113,12 @@ export async function proxyFetch(
|
|||
const targetUrl = input instanceof Request ? input.url : input.toString();
|
||||
const dispatcher = getProxyAgent(targetUrl, timeoutOptions, lookup);
|
||||
|
||||
return await fetch(input, {
|
||||
...init,
|
||||
// @ts-expect-error - dispatcher is an undici-specific option not in standard fetch
|
||||
// The dispatcher comes from this package's undici, so the request must use
|
||||
// the same undici's fetch: the global fetch on Node >= 26 rejects it.
|
||||
return (await undiciFetch(input as Parameters<typeof undiciFetch>[0], {
|
||||
...(init as Parameters<typeof undiciFetch>[1]),
|
||||
dispatcher,
|
||||
});
|
||||
})) as unknown as Response;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,8 +1,13 @@
|
|||
import { proxyFetch } from '@n8n/ai-utilities';
|
||||
import type { ILoadOptionsFunctions } from 'n8n-workflow';
|
||||
import type { Mocked } from 'vitest';
|
||||
|
||||
import { searchModels } from '../searchModels';
|
||||
|
||||
vi.mock('@n8n/ai-utilities', () => ({
|
||||
proxyFetch: vi.fn(),
|
||||
}));
|
||||
|
||||
const mockModels = [
|
||||
{
|
||||
id: 'claude-3-opus-20240229',
|
||||
|
|
@ -51,11 +56,12 @@ describe('searchModels', () => {
|
|||
json: async () => ({ data: mockModels }),
|
||||
text: async () => '',
|
||||
});
|
||||
vi.stubGlobal('fetch', fetchSpy);
|
||||
vi.mocked(proxyFetch).mockImplementation(
|
||||
fetchSpy as unknown as typeof import('@n8n/ai-utilities')['proxyFetch'],
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals();
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { getProxyAgent } from '@n8n/ai-utilities';
|
||||
import { proxyFetch } from '@n8n/ai-utilities';
|
||||
import { listAnthropicModels } from '@n8n/ai-utilities/model-discovery';
|
||||
import type { ILoadOptionsFunctions, INodeListSearchResult } from 'n8n-workflow';
|
||||
|
||||
|
|
@ -19,11 +19,7 @@ export async function searchModels(
|
|||
apiKey: (credentials.apiKey as string) ?? '',
|
||||
baseURL,
|
||||
headers: mergeCustomHeaders(credentials, {}),
|
||||
fetch: async (url, init) =>
|
||||
await fetch(url, {
|
||||
...init,
|
||||
dispatcher: getProxyAgent(baseURL),
|
||||
} as RequestInit),
|
||||
fetch: proxyFetch,
|
||||
});
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,13 @@
|
|||
import { proxyFetch } from '@n8n/ai-utilities';
|
||||
import type { ILoadOptionsFunctions } from 'n8n-workflow';
|
||||
import type { Mocked } from 'vitest';
|
||||
|
||||
import { searchModels } from '../loadModels';
|
||||
|
||||
vi.mock('@n8n/ai-utilities', () => ({
|
||||
proxyFetch: vi.fn(),
|
||||
}));
|
||||
|
||||
const MODEL_IDS = [
|
||||
'gpt-4',
|
||||
'gpt-3.5-turbo',
|
||||
|
|
@ -43,7 +48,9 @@ describe('searchModels', () => {
|
|||
json: async () => ({ data: MODEL_IDS.map((id) => ({ id })) }),
|
||||
text: async () => '',
|
||||
});
|
||||
vi.stubGlobal('fetch', fetchSpy);
|
||||
vi.mocked(proxyFetch).mockImplementation(
|
||||
fetchSpy as unknown as typeof import('@n8n/ai-utilities')['proxyFetch'],
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { getProxyAgent } from '@n8n/ai-utilities';
|
||||
import { proxyFetch } from '@n8n/ai-utilities';
|
||||
import { listOpenAiModels } from '@n8n/ai-utilities/model-discovery';
|
||||
import { AiConfig } from '@n8n/config';
|
||||
import { Container } from '@n8n/di';
|
||||
|
|
@ -24,11 +24,7 @@ export async function searchModels(
|
|||
apiKey: credentials.apiKey as string,
|
||||
baseURL,
|
||||
headers,
|
||||
fetch: async (url, init) =>
|
||||
await fetch(url, {
|
||||
...init,
|
||||
dispatcher: getProxyAgent(baseURL),
|
||||
} as RequestInit),
|
||||
fetch: proxyFetch,
|
||||
});
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,12 @@
|
|||
/* eslint-disable n8n-nodes-base/node-filename-against-convention */
|
||||
/* eslint-disable @typescript-eslint/unbound-method */
|
||||
import { ChatAnthropic } from '@langchain/anthropic';
|
||||
import { N8nLlmTracing, makeN8nLlmFailedAttemptHandler, getProxyAgent } from '@n8n/ai-utilities';
|
||||
import {
|
||||
N8nLlmTracing,
|
||||
makeN8nLlmFailedAttemptHandler,
|
||||
getProxyAgent,
|
||||
proxyFetch,
|
||||
} from '@n8n/ai-utilities';
|
||||
import { createMockExecuteFunction } from 'n8n-nodes-base/test/nodes/Helpers';
|
||||
import type { ILoadOptionsFunctions, INode, ISupplyDataFunctions } from 'n8n-workflow';
|
||||
import type { Mock, Mocked } from 'vitest';
|
||||
|
|
@ -425,7 +430,9 @@ describe('LmChatAnthropic', () => {
|
|||
beforeEach(() => {
|
||||
mockGetCredentials = vi.fn();
|
||||
fetchSpy = vi.fn();
|
||||
vi.stubGlobal('fetch', fetchSpy);
|
||||
vi.mocked(proxyFetch).mockImplementation(
|
||||
fetchSpy as unknown as typeof import('@n8n/ai-utilities')['proxyFetch'],
|
||||
);
|
||||
|
||||
mockLoadContext = {
|
||||
getCredentials: mockGetCredentials,
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user