fix(ag-ui):subagent event forward#1870
Conversation
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
AgentScopeJavaBot
left a comment
There was a problem hiding this comment.
🤖 AI Review
This PR fixes the AG-UI adapter to correctly forward sub-agent intermediate events (reasoning/text) to the frontend when SubAgentTool has forwardEvents=true. The fix adds a new branch for non-last TOOL_RESULT events, extracting the inner Event from metadata and recursively converting it. Key concerns: (1) subagent_event magic string is a cross-module contract with no shared constant; (2) recursive convertEvent shares EventConversionState, risking currentTextMessageId state pollution; (3) no null-check on the inner Event extracted from metadata.
| if (block instanceof ToolResultBlock toolResult) { | ||
| Map<String, Object> metadata = toolResult.getMetadata(); | ||
| if (metadata != null) { | ||
| Object inner = metadata.get("subagent_event"); |
There was a problem hiding this comment.
[major] subagent_event is a cross-module magic string shared between SubAgentTool (core) and this adapter (extensions). A typo here silently drops all sub-agent events. Extract to a shared constant, e.g. SubAgentTool.METADATA_KEY_SUBAGENT_EVENT, and add a debug log when the metadata value is not an Event instance.
| if (block instanceof ToolResultBlock toolResult) { | ||
| Map<String, Object> metadata = toolResult.getMetadata(); | ||
| if (metadata != null) { | ||
| Object inner = metadata.get("subagent_event"); |
There was a problem hiding this comment.
[major] Recursive convertEvent(innerEvent, state) shares the same EventConversionState. If the sub-agent stream terminates without a final isLast=true REASONING event (e.g. cancellation, error), currentTextMessageId remains stuck on the sub-agent message ID, potentially corrupting subsequent supervisor text messages. Consider saving/restoring currentTextMessageId around the recursive call.
There was a problem hiding this comment.
Won't fix. finishRun() already emits trailing TextMessageEnd for any unended message
| if (inner instanceof Event innerEvent) { | ||
| events.addAll(convertEvent(innerEvent, state)); | ||
| } | ||
| } |
There was a problem hiding this comment.
[minor] No null/instance check on the value extracted from metadata. If the metadata contains a non-Event object, the recursive convertEvent call will fail with a ClassCastException. Add a defensive instanceof check before the recursive call.
There was a problem hiding this comment.
Won't fix. inner instanceof Event innerEvent already does both null- and type-checking via pattern matching.won't fix
|
Good refactor! Extracting metadata keys as public constants makes them usable by downstream consumers without string duplication. The AG-UI adapter integration looks clean — forwarding sub-agent events through the metadata channel is a pragmatic approach. |
63f30de to
66497cb
Compare
66497cb to
280ffcf
Compare
Background
Fixes #1046 When a supervisor agent delegates to a sub-agent via SubAgentTool with SubAgentConfig.forwardEvents(true), the sub-agent's intermediate
reasoning/text was completely invisible to the AG-UI frontend — the UI saw TOOL_CALL_START, then a long pause, then TOOL_CALL_END + TOOL_CALL_RESULT arriving together. Effectively a "black box" during sub-agent execution,
especially painful for multi-step researchers/analysts.
Changes