Surface AG-UI REASONING_MESSAGE_CHUNK events as reasoning content - #936
Conversation
The client onEvent switch handles ReasoningMessageContentEvent but not the semantically equivalent ReasoningMessageChunkEvent, which the AG-UI decoder also produces (decoder EventTypeReasoningMessageChunk). A server that streams reasoning as chunk events therefore has its reasoning silently dropped. Add a ReasoningMessageChunkEvent case that emits a TextReasoningContent, reusing the last chunk MessageID for chunks that omit it - mirroring the existing TextMessageChunkEvent handling and ReasoningMessageContentEvent.
There was a problem hiding this comment.
Pull request overview
Adds support in the AG-UI provider for streaming reasoning deltas sent as REASONING_MESSAGE_CHUNK events, so chunked reasoning is surfaced to collectors instead of being dropped.
Changes:
- Handle
*aguiEvents.ReasoningMessageChunkEventintoolCallAccumulator.onEventby emittingmessage.TextReasoningContentupdates. - Add a unit test that streams reasoning chunk events and asserts the concatenated reasoning is surfaced.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| provider/aguiprovider/agui.go | Adds a switch-case to map ReasoningMessageChunkEvent into TextReasoningContent response updates (with MessageID continuation logic). |
| provider/aguiprovider/agui_test.go | Adds a test asserting chunked reasoning is collected and exposed as TextReasoningContent. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| case *aguiEvents.ReasoningMessageChunkEvent: | ||
| delta := deref(e.Delta) | ||
| if delta == "" { | ||
| return nil, nil | ||
| } | ||
| // A chunk may omit MessageID to continue the current reasoning message; | ||
| // reuse the last seen chunk MessageID, mirroring the text-chunk handling. | ||
| if id := deref(e.MessageID); id != "" { | ||
| a.lastChunkMessageID = id | ||
| } | ||
| return []*agent.ResponseUpdate{{ | ||
| Role: message.RoleAssistant, | ||
| MessageID: a.lastChunkMessageID, | ||
| CreatedAt: eventTime(evt), | ||
| Contents: message.Contents{&message.TextReasoningContent{Text: delta}}, |
| server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
| w.Header().Set("Content-Type", "text/event-stream") | ||
| writeSSE(t, w, aguiEvents.NewRunStartedEvent("thread-1", "run-1")) | ||
| writeSSE(t, w, aguiEvents.NewReasoningMessageChunkEvent(strPtr("r1"), strPtr("think"))) | ||
| writeSSE(t, w, aguiEvents.NewReasoningMessageChunkEvent(strPtr("r1"), strPtr("ing"))) | ||
| writeSSE(t, w, aguiEvents.NewRunFinishedEvent("thread-1", "run-1")) | ||
| })) | ||
| defer server.Close() | ||
|
|
||
| a := aguiprovider.NewAgent(newTestClient(server.URL), aguiprovider.AgentConfig{}) | ||
| resp, err := a.RunText(context.Background(), "hi").Collect() | ||
| if err != nil { | ||
| t.Fatalf("run error: %v", err) | ||
| } | ||
| var reasoning string | ||
| for content := range resp.Contents() { | ||
| if rc, ok := content.(*message.TextReasoningContent); ok { | ||
| reasoning += rc.Text | ||
| } | ||
| } | ||
| if reasoning != "thinking" { | ||
| t.Fatalf("reasoning text = %q, want %q", reasoning, "thinking") | ||
| } |
Quim Muntal (qmuntal)
left a comment
There was a problem hiding this comment.
Fix copilot feedback.
Parity Review SummaryScope: User-visible behavior (provider/aguiprovider)
Result: ✅ Aligned / no parity issues This is a self-contained bug fix in the Go AG-UI client:
|
Problem
The AG-UI client's
onEventswitch (provider/aguiprovider/agui.go) handlesReasoningMessageContentEventandTextMessageChunkEvent, but has no case forReasoningMessageChunkEvent— the chunk-form encoding of reasoning that the AG-UI decoder also produces (decoder.gocaseEventTypeReasoningMessageChunk). A server that streams reasoning asREASONING_MESSAGE_CHUNKevents therefore has its reasoning silently dropped (falls through to the default).This is the reasoning counterpart of the already-handled
TEXT_MESSAGE_CHUNKcase.Fix
Add a
*aguiEvents.ReasoningMessageChunkEventcase that emits amessage.TextReasoningContentfrom the chunk delta, reusing the last seen chunkMessageIDwhen a chunk omits it — mirroring the existingTextMessageChunkEventhandling and theReasoningMessageContentEventmapping.Test
TestAGUIAgentRun_SurfacesReasoningMessageChunkEventsstreams twoREASONING_MESSAGE_CHUNKevents and asserts the concatenated reasoning is surfaced asTextReasoningContent. Fails before the fix (empty), passes after.Note:
TOOL_CALL_CHUNKis intentionally not addressed here — the AG-UI Go decoder has no case for it, so it never reaches the client.