Skip to content

fix(agui): migrate adapter to v2 streamEvents and bridge HITL confirmations (#2437) - #2473

Closed
wzq-xzwj wants to merge 1 commit into
agentscope-ai:mainfrom
wzq-xzwj:fix/agui-streamevents-migration
Closed

fix(agui): migrate adapter to v2 streamEvents and bridge HITL confirmations (#2437)#2473
wzq-xzwj wants to merge 1 commit into
agentscope-ai:mainfrom
wzq-xzwj:fix/agui-streamevents-migration

Conversation

@wzq-xzwj

Copy link
Copy Markdown
Contributor

Summary

Fixes #2437 — the AG-UI adapter dropped human-in-the-loop (HITL) tool-confirmation events, so HITL was completely non-functional over the AG-UI protocol.

The adapter consumed the deprecated v1 Flux<Event> stream, which only surfaces coarse REASONING / SUMMARY / TOOL_RESULT events and cannot carry HITL signals (RequireUserConfirmEvent / RequestStopEvent). Consequently a paused run either hung or ran to a bare completion; the client was never told a confirmation was required, and an approved call could not be resumed.

What this PR does

  1. New capability interface EventStreamingAgent (io.agentscope.core.agent)

    • Declares Flux<AgentEvent> streamEvents(List<Msg>, RuntimeContext), the modern replacement for the deprecated v1 StreamableAgent.stream(...).
    • Both ReActAgent and HarnessAgent (which wraps a ReActAgent delegate) implement it, so the adapter can branch on instanceof EventStreamingAgent and consume the v2 stream uniformly regardless of the concrete agent type — while still falling back to the deprecated v1 path for other Agent implementations.
  2. Adapter migrated to the v2 Flux<AgentEvent> stream

    • Maps each typed event (text / thinking / tool-call / tool-result blocks) to the corresponding AG-UI events.
  3. Bidirectional HITL bridging

    • Outbound: when a run pauses on RequireUserConfirmEvent, RUN_FINISHED now carries a RunFinishedInterruptOutcome with one Interrupt per pending tool call (reason "tool_confirmation"), instead of a bare completion.
    • Inbound: confirmation results supplied via forwardedProps["agentscope_confirm_results"] are parsed into List<ConfirmResult> and attached to the resumed message under Msg.METADATA_CONFIRM_RESULTS, letting a paused ReActAgent apply them and continue. The reconstructed ToolUseBlock also carries a raw-args JSON string in content, because the tool executor validates ToolUseBlock.getContent() (not the parsed input map) and applying a ConfirmResult fully replaces the stored block.

Testing

  • Unit: adds AguiAgentAdapterV2Test (11 tests) covering the v2 event-mapping path and both HITL directions. The full agentscope-extensions-agui suite passes (339 tests).
  • End-to-end: verified against a local Harness + MCP (SSE) setup with a non-readonly echo tool under PermissionMode.DEFAULT:
    • Step 1 — the echo call pauses: RUN_FINISHED with outcome.type=interrupt (reason: "tool_confirmation"), and the tool is not executed.
    • Step 2 — resuming with forwardedProps.agentscope_confirm_results executes the tool and returns the real result (TOOL_CALL_RESULT content "echo: hello-hitl"), followed by a normal RUN_FINISHED.

Wire example

Outbound interrupt (RUN_FINISHED):

{"type":"RUN_FINISHED","outcome":{"type":"interrupt","interrupts":[
  {"id":"call_...","reason":"tool_confirmation",
   "message":"Tool call 'echo' requires confirmation before it can run.",
   "toolCallId":"call_..."}]}}

Inbound resume (forwardedProps):

{"agentscope_confirm_results":[
  {"toolCallId":"call_...","confirmed":true,"toolName":"echo",
   "input":{"message":"hello-hitl"}}]}

…ations (agentscope-ai#2437)

The AG-UI adapter consumed the deprecated v1 `Flux<Event>` stream, which
only surfaces coarse REASONING/SUMMARY/TOOL_RESULT events and cannot carry
human-in-the-loop signals. As a result, tool-confirmation events
(RequireUserConfirmEvent) were silently dropped: over AG-UI, HITL was
completely non-functional -- a paused run either hung or ran to a bare
completion, and the client was never told a confirmation was required, nor
could it resume an approved call.

This change:

* Adds `EventStreamingAgent`, a small capability interface in
  `io.agentscope.core.agent` declaring
  `Flux<AgentEvent> streamEvents(List<Msg>, RuntimeContext)`. Both
  `ReActAgent` and `HarnessAgent` implement it, so the adapter can consume
  the fine-grained v2 stream uniformly via `instanceof EventStreamingAgent`,
  independent of the concrete agent type, and still fall back to the
  deprecated v1 path for other `Agent` implementations.

* Migrates `AguiAgentAdapter` to consume the v2 `Flux<AgentEvent>` stream and
  map each typed event (text/thinking/tool-call/tool-result blocks) to the
  corresponding AG-UI events.

* Bridges HITL in both directions:
  - Outbound: when a run pauses on RequireUserConfirmEvent, RUN_FINISHED now
    carries a RunFinishedInterruptOutcome with one Interrupt per pending tool
    call (reason "tool_confirmation"), so the client learns a confirmation is
    required instead of seeing a bare completion.
  - Inbound: confirmation results supplied via
    `forwardedProps["agentscope_confirm_results"]` are parsed into
    `List<ConfirmResult>` and attached to the resumed message under
    `Msg.METADATA_CONFIRM_RESULTS`, letting a paused ReActAgent apply them
    and continue. The reconstructed ToolUseBlock also carries a raw-args JSON
    string in `content`, because the tool executor validates
    `ToolUseBlock.getContent()` (not the parsed input map) and applying a
    ConfirmResult fully replaces the stored block.

Adds AguiAgentAdapterV2Test (11 tests) covering the v2 mapping path and both
HITL directions. Verified end-to-end against a local Harness + MCP setup: an
echo tool call pauses with an interrupt outcome and is not executed, and a
follow-up resume with confirmation results executes it and returns the real
result.

Closes agentscope-ai#2437
@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@codecov

codecov Bot commented Jul 29, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 63.84977% with 77 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...agentscope/core/agui/adapter/AguiAgentAdapter.java 63.84% 38 Missing and 39 partials ⚠️

📢 Thoughts on this report? Let us know!

@wzq-xzwj

Copy link
Copy Markdown
Contributor Author

Thanks @jujn for the pointer to #2306 — I hadn't seen it when I opened this.

#2306 already covers the root cause of #2437 (the v1 stream() → v2 streamEvents() migration) and does it more comprehensively: it wires HITL through the official RunAgentInput.resume[] contract and enforces the AG-UI interrupt rules, whereas this PR bridged resumes through a custom forwardedProps["agentscope_confirm_results"] key. The resume[] approach is the more standard one, and since #2306 is already approved and mergeable, keeping this open would only create conflicts.

Closing this in favor of #2306. I'll follow up on #2437 to confirm it's resolved once #2306 lands.

One small thing I ran into during end-to-end testing that might be worth a quick check against #2306's resume path: when a ConfirmResult's ToolUseBlock is rebuilt on resume, the tool executor validates ToolUseBlock.getContent() (the raw-args JSON string), not the parsed input map — and applyConfirmResults fully replaces the stored block. If the reconstructed block only carries input and not content, the resumed tool call fails schema validation with argument "content" is null. Flagging in case it's relevant.

Thanks again for the coordination!

@oss-maintainer oss-maintainer left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review — Approved ✅

Summary

Migrates the AG-UI adapter from the deprecated v1 Flux<Event> stream to the v2 Flux<AgentEvent> stream, and adds bidirectional HITL (human-in-the-loop) bridging for tool confirmation.

Changes Reviewed

1. New EventStreamingAgent interface (agentscope-core)

  • Clean capability interface — right pattern for opt-in v2 streaming
  • Properly documented as replacement for deprecated StreamableAgent.stream()
  • ReActAgent and HarnessAgent both implement it; adapter branches on instanceof

2. Adapter migration to v2 stream (AguiAgentAdapter)

  • streamAgentEvents() now checks instanceof EventStreamingAgent first, falls back to v1 for other agents — good backward compatibility
  • EventConversionState tracks active messages, tool calls, and reasoning state for proper event ordering
  • Handles edge cases: orphan deltas without start events, tool results without explicit starts

3. HITL outbound bridging

  • RequireUserConfirmEvent → closes dangling text/reasoning messages → RunFinishedInterruptOutcome with Interrupt per pending tool call
  • Reason is "tool_confirmation" — clear and actionable for clients

4. HITL inbound bridging

  • Parses forwardedProps["agentscope_confirm_results"] into List<ConfirmResult>
  • Attaches to last message under Msg.METADATA_CONFIRM_RESULTS
  • Correctly handles the ToolUseBlock.getContent() requirement (raw-args JSON string) for tool executor validation
  • Synthesizes a minimal user message when no carrier message exists

5. Test coverage (AguiAgentAdapterV2Test, 11 tests)

  • Text/thinking/tool-call event mapping
  • Reasoning gated by config
  • Tool call args suppression
  • HITL outbound: interrupt outcome on RequireUserConfirmEvent
  • HITL inbound: confirm results attached to resumed message
  • Edge cases: orphan deltas, no forwarded results

6. CI: All green — build ✅, codecov ✅, CLA ✅, license ✅

Minor Observations (non-blocking)

  • The ToolUseBlock.getContent() carrying raw-args JSON is a workaround for the tool executor validation. A future cleanup could relax that validation to accept the parsed input map directly, but that's out of scope here.
  • The EventConversionState class is getting substantial state-tracking logic. Consider extracting it to its own file if it grows further.

Verdict

Well-structured architectural migration with comprehensive HITL support. The fallback to v1 ensures no breaking changes for custom agent implementations. LGTM.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AG-UI adapter drops human-in-the-loop tool confirmation (consumes deprecated v1 Event stream; RequireUserConfirmEvent never bridged)

2 participants