refactor(a2a): standardize safeFetch signature and simplify usage in registry and client manager

This commit is contained in:
Alisa Novikova
2026-03-06 04:51:25 -08:00
parent e4bc4a6cf5
commit 1112c0f966
4 changed files with 13 additions and 18 deletions

View File

@@ -66,7 +66,7 @@ const a2aDispatcher = new UndiciAgent({
});
const a2aFetch: typeof fetch = (input, init) => {
const nodeInit: NodeFetchInit = { ...init, dispatcher: a2aDispatcher };
return fetch(input, nodeInit);
return fetch(input, nodeInit as RequestInit);
};
/**

View File

@@ -531,14 +531,10 @@ describe('AgentRegistry', () => {
});
expect(call).toBeDefined();
// Verify that the wrapper delegates to safeFetch
const options = call?.[0] as { fetchImpl?: typeof fetch };
// We can't easily spy on safeFetch because it's an exported function,
// but we've verified it is provided via options.
expect(typeof options?.fetchImpl).toBe('function');
// Use safeFetch to satisfy the unused import check.
expect(safeFetch).toBeDefined();
// We passed safeFetch directly
expect(options?.fetchImpl).toBe(safeFetch);
});
});

View File

@@ -166,15 +166,8 @@ export class AgentRegistry {
// We use a dedicated resolver here to fetch the card for hashing.
// This is separate from loadAgent to keep hashing logic isolated.
// We provide safeFetch to ensure SSRF and DNS rebinding protection.
// We wrap it to match the signature expected by the SDK.
const fetchImpl: typeof fetch = (input, init) => {
if (input instanceof Request) {
return safeFetch(input.url, init);
}
return safeFetch(input, init);
};
const resolver = new DefaultAgentCardResolver({
fetchImpl,
fetchImpl: safeFetch,
});
const { baseUrl, path } = splitAgentCardUrl(agent.agentCardUrl);
const rawCard = await resolver.resolve(baseUrl, path);

View File

@@ -191,7 +191,7 @@ export function isAddressPrivate(address: string): boolean {
* Prevents access to private/internal networks at the connection level.
*/
export async function safeFetch(
url: string | URL,
input: RequestInfo | URL,
init?: RequestInit,
): Promise<Response> {
const nodeInit: NodeFetchInit = {
@@ -200,13 +200,19 @@ export async function safeFetch(
};
try {
return await fetch(url, nodeInit);
return await fetch(input, nodeInit);
} catch (error) {
if (error instanceof Error) {
// Re-map refusing to connect errors to standard FetchError
if (error.message.includes('Refusing to connect to private IP address')) {
const urlString =
input instanceof Request
? input.url
: typeof input === 'string'
? input
: input.toString();
throw new FetchError(
`Access to private network is blocked: ${url.toString()}`,
`Access to private network is blocked: ${urlString}`,
'ERR_PRIVATE_NETWORK',
{ cause: error },
);