feature: add A2A Protocol Client Support#6311
feature: add A2A Protocol Client Support#6311rohan-patil2 wants to merge 3 commits intoFlowiseAI:mainfrom
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces the A2A (Agent-to-Agent) remote agent integration, including new nodes for Agentflow and tools, a client SDK wrapper, and necessary configuration updates. The implementation includes security measures such as host allowlisting, URL scheme validation, and error message sanitization. My feedback focuses on improving the robustness of the custom fetch implementation, specifically regarding 304 response handling, memory management for abort signals, and refining label sanitization logic.
| // defeat the auth-header scoping above (Authorization/X-API-Key could | ||
| // be replayed to the redirect target) and reopen the SSRF surface by | ||
| // sending traffic to arbitrary hosts. | ||
| if (res.status >= 300 && res.status < 400) { |
There was a problem hiding this comment.
The check res.status >= 300 && res.status < 400 will throw an error for 304 Not Modified responses. While 304 is technically in the 3xx range, it is not a redirection and is a standard response for conditional requests (caching). This could cause unexpected failures if the remote server or a proxy returns a 304. It is safer to explicitly exclude 304 from the redirect block.
| if (res.status >= 300 && res.status < 400) { | |
| if (res.status >= 300 && res.status < 400 && res.status !== 304) { |
| const merged = new AbortController() | ||
| const onAbort = () => merged.abort() | ||
| if (active.aborted || callerSignal.aborted) { | ||
| merged.abort() | ||
| } else { | ||
| active.addEventListener('abort', onAbort, { once: true }) | ||
| callerSignal.addEventListener('abort', onAbort, { once: true }) | ||
| } | ||
| return merged.signal |
There was a problem hiding this comment.
The fallback implementation for AbortSignal.any attaches event listeners to active and callerSignal but never removes them. This can lead to memory leaks, especially if callerSignal (which may be the long-lived externalAbortSignal) accumulates listeners over many requests that complete successfully. Consider adding a cleanup mechanism to remove these listeners once the fetch operation settles.
| } | ||
|
|
||
| const returnRole = returnResponseAs === 'assistantMessage' ? 'assistant' : 'user' | ||
| const nodeLabel = nodeData?.label ? nodeData.label.toLowerCase().replace(/\s/g, '_').trim() : nodeData?.id |
There was a problem hiding this comment.
The sanitization logic for nodeLabel is flawed because trim() is called after replace(), and it doesn't handle multiple consecutive spaces well. This can result in names starting or ending with underscores. Calling trim() first and using a regex that collapses multiple spaces is more robust and improves readability, aligning with the preference for clear, chained operations over complex logic.
| const nodeLabel = nodeData?.label ? nodeData.label.toLowerCase().replace(/\s/g, '_').trim() : nodeData?.id | |
| const nodeLabel = nodeData?.label ? nodeData.label.trim().toLowerCase().replace(/\s+/g, '_') : nodeData?.id |
References
- Prioritize code readability and understandability over conciseness, favoring a series of simple, chained operations over single complex ones.
|
some tests from CI are failing, can you investigate why? |
Yes, working on them. |
dc072a1 to
e78de46
Compare
e78de46 to
759009a
Compare
Summary
This PR adds Agent-to-Agent (A2A) protocol client support to Flowise, enabling agentflows to discover, authenticate with, and invoke external A2A-compatible agents. This turns Flowise into an A2A protocol client that can orchestrate work across any A2A-compliant agent, regardless of framework, vendor, or hosting.
The implementation targets A2A Protocol Specification v0.3.0 (aligned with
@a2a-js/sdk@0.3.x).What's included
@a2a-js/sdkwith auth, timeouts, abort support, host allowlisting, SSRF protections, and error sanitizationDynamicToolthat lets Flowise Agent nodes call A2A agents as toolsWhat's NOT included (out of scope)
FilePart)