Skip to content

Surface inbound AG-UI CUSTOM events as metadata instead of dropping them#694

Open
PratikDhanave wants to merge 2 commits into
microsoft:mainfrom
PratikDhanaveFork:surface-agui-custom-events
Open

Surface inbound AG-UI CUSTOM events as metadata instead of dropping them#694
PratikDhanave wants to merge 2 commits into
microsoft:mainfrom
PratikDhanaveFork:surface-agui-custom-events

Conversation

@PratikDhanave

Copy link
Copy Markdown
Contributor

What

The AG-UI event accumulator in provider/aguiprovider/agui.go silently dropped inbound CUSTOM events. (*toolCallAccumulator).onEvent had no case for *events.CustomEvent, so a decoded CUSTOM frame fell straight through to the default: arm and returned nil, nil — the event never reached the caller even though the decoder handled the CUSTOM wire type without error.

This adds a case that emits a single metadata-only assistant ResponseUpdate (no Contents) with the event surfaced under AdditionalProperties["agui_custom_event"] as {name, value}. The existing run() loop already lazily initializes AdditionalProperties and injects agui_thread_id, so the update flows through unchanged.

Why

CUSTOM is a first-class AG-UI event type used for application-specific signals (e.g. predictive_state). The .NET and Python AG-UI clients expose custom events to callers rather than discarding them; dropping them here is a cross-SDK parity gap. Surfacing them as message metadata keeps them out of the assistant text/content stream while still making them observable to consumers.

How tested

Added TestAGUIAgentRun_SurfacesCustomEventAsMetadata in agui_test.go: an httptest SSE server emits NewCustomEvent("predictive_state", WithValue(map[string]any{"foo":"bar"})), the agent runs, and the test asserts one collected message carries AdditionalProperties["agui_custom_event"] with the expected name and value map. The test fails before the fix (no such update is emitted) and passes after. go build ./..., go vet ./provider/aguiprovider/..., and go test ./provider/aguiprovider/... all pass.

Copilot AI review requested due to automatic review settings July 24, 2026 01:55
@PratikDhanave
PratikDhanave requested a review from a team as a code owner July 24, 2026 01:55

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This pull request updates the AG-UI provider’s event-to-response mapping so inbound CUSTOM AG-UI events are no longer silently dropped, and become observable to callers as response/message metadata.

Changes:

  • Handle *aguiEvents.CustomEvent in (*toolCallAccumulator).onEvent by emitting a metadata-only ResponseUpdate containing AdditionalProperties["agui_custom_event"].
  • Add an integration-style SSE test to verify a CUSTOM event is surfaced into collected response messages.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
provider/aguiprovider/agui.go Adds a CustomEvent case that converts inbound CUSTOM frames into a metadata-only assistant update.
provider/aguiprovider/agui_test.go Adds a test that streams a CUSTOM event and asserts it is surfaced via AdditionalProperties["agui_custom_event"].

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +482 to +492
case *aguiEvents.CustomEvent:
return []*agent.ResponseUpdate{{
Role: message.RoleAssistant,
CreatedAt: eventTime(evt),
AdditionalProperties: map[string]any{
"agui_custom_event": map[string]any{
"name": e.Name,
"value": e.Value,
},
},
}}, nil

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch — fixed in 77bdb0a: each custom event now gets a unique synthetic MessageID (agui-custom-<n>) so it forms its own message instead of being merged into the last assistant message. This stops multiple custom events from overwriting each other under the shared agui_custom_event key via maps.Copy, and keeps them off unrelated assistant content. Added a black-box test that fails without the fix.

@github-actions

This comment has been minimized.

@PratikDhanave
PratikDhanave force-pushed the surface-agui-custom-events branch from 801b2a1 to ce9dff7 Compare July 24, 2026 02:14
@github-actions

This comment has been minimized.

@github-actions github-actions Bot added the parity-approved Go API consistency review found no parity issues label Jul 24, 2026
The AG-UI event accumulator dropped decoded CUSTOM events: onEvent had no
case for *events.CustomEvent, so every CUSTOM frame fell through to the
default arm and was silently discarded.

Emit a metadata-only assistant ResponseUpdate carrying the event name and
value under AdditionalProperties["agui_custom_event"], matching how the
.NET and Python AG-UI clients expose custom events to callers rather than
swallowing them.
Custom events previously emitted a ResponseUpdate with no MessageID, so
they were merged into the last assistant message. Because Response.Update
uses maps.Copy for AdditionalProperties, multiple custom events in a
stream overwrote each other under the shared agui_custom_event key and
attached to unrelated assistant content. Assign each custom event a unique
synthetic MessageID so it forms its own message and is preserved.
@PratikDhanave
PratikDhanave force-pushed the surface-agui-custom-events branch from 77bdb0a to 77bed09 Compare July 24, 2026 09:34
@github-actions

This comment has been minimized.

@github-actions github-actions Bot added public-api-change Pull Request changes public APIs and removed parity-approved Go API consistency review found no parity issues labels Jul 24, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Cross-repo parity review — PR #694

Scope: provider/aguiprovider/agui.go — surfaces inbound AG-UI CUSTOM events as AdditionalProperties metadata on agent.ResponseUpdate instead of silently dropping them. This adds a new user-visible key to the public AdditionalProperties map and is therefore in scope for parity review.

Upstream reference: python/packages/ag-ui/agent_framework_ag_ui/_event_converters.py_handle_custom_event, added in the same recent work. No equivalent .NET surface was found that handles CUSTOM events at the provider level.


Parity issues found

1. AdditionalProperties key name diverges from Python

SDK Key used
Go (this PR) "agui_custom_event"
Python "ag_ui_custom_event"

Python uses ag_ui_ (matching the "AG-UI" protocol) while Go uses agui_. Callers that introspect AdditionalProperties cross-SDK will need different key spellings. The Go key should be "ag_ui_custom_event" to stay consistent.

2. thread_id / run_id not included in Go custom-event response

Python's _handle_custom_event always populates thread_id and run_id in additional_properties alongside the custom-event payload (these are tracked state on the converter). The Go implementation omits them, so consumers of the custom-event update lose correlation context they get from the Python SDK.

3. raw_type field not present in Go

Python records the original event type string (raw_event_type) as "raw_type" in the custom event's metadata object. This allows callers to distinguish between CUSTOM and CUSTOM_EVENT spellings at runtime. The Go path handles only *aguiEvents.CustomEvent (a single typed variant) so there may not be an equivalent distinction, but if there is, it should be surfaced for parity.


Upstream Python surface: microsoft/agent-frameworkpython/packages/ag-ui/agent_framework_ag_ui/_event_converters.py_handle_custom_event

Recommend aligning the Go key name to "ag_ui_custom_event" and adding thread_id/run_id correlation fields to match Python semantics before merging.

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • awmgmcpg

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "awmgmcpg"

See Network Configuration for more information.

Generated by Go API Consistency Review Agent · 45 AIC · ⌖ 4.53 AIC · ⊞ 5.9K ·

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • awmgmcpg

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "awmgmcpg"

See Network Configuration for more information.

Generated by Go API Consistency Review Agent · 45 AIC · ⌖ 4.53 AIC · ⊞ 5.9K

MessageID: fmt.Sprintf("agui-custom-%d", a.customSeq),
CreatedAt: eventTime(evt),
AdditionalProperties: map[string]any{
"agui_custom_event": map[string]any{

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Parity issue — key name diverges from Python SDK

The upstream Python _handle_custom_event uses "ag_ui_custom_event" (with underscore after ag) as the additional_properties key. This PR uses "agui_custom_event" (no underscore). Callers inspecting AdditionalProperties cross-SDK will need different spellings.

Suggested rename to "ag_ui_custom_event" to match microsoft/agent-framework_event_converters.py.

CreatedAt: eventTime(evt),
AdditionalProperties: map[string]any{
"agui_custom_event": map[string]any{
"name": e.Name,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Parity issue — missing thread_id/run_id correlation fields

The Python _handle_custom_event includes thread_id and run_id in the additional_properties alongside the custom-event payload (they are tracked state in the converter). Go omits them, so consumers of this update lose run-correlation context that Python callers receive.

Consider adding the AG-UI thread/run IDs here to keep observability parity with the Python SDK.

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

Labels

public-api-change Pull Request changes public APIs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants