From 55283c742bfb55a3af38b6f128fff93d12f078ef Mon Sep 17 00:00:00 2001 From: PratikDhanave Date: Fri, 28 Aug 2026 11:16:43 +0530 Subject: [PATCH] Surface AG-UI REASONING_MESSAGE_CHUNK events as reasoning content 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. --- provider/aguiprovider/agui.go | 16 ++++++++++++++++ provider/aguiprovider/agui_test.go | 27 +++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/provider/aguiprovider/agui.go b/provider/aguiprovider/agui.go index 050a2403..d9cd0165 100644 --- a/provider/aguiprovider/agui.go +++ b/provider/aguiprovider/agui.go @@ -446,6 +446,22 @@ func (a *toolCallAccumulator) onEvent(evt aguiEvents.Event) ([]*agent.ResponseUp CreatedAt: eventTime(evt), Contents: message.Contents{&message.TextContent{Text: delta}}, }}, nil + 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}}, + }}, nil case *aguiEvents.ReasoningMessageContentEvent: return []*agent.ResponseUpdate{{ Role: message.RoleAssistant, diff --git a/provider/aguiprovider/agui_test.go b/provider/aguiprovider/agui_test.go index cd0a0847..22a4e976 100644 --- a/provider/aguiprovider/agui_test.go +++ b/provider/aguiprovider/agui_test.go @@ -713,3 +713,30 @@ func writeSSE(t *testing.T, w http.ResponseWriter, evt aguiEvents.Event) { func newTestClient(endpoint string) *aguiSSEClient.Client { return aguiSSEClient.NewClient(aguiSSEClient.Config{Endpoint: endpoint}) } + +func TestAGUIAgentRun_SurfacesReasoningMessageChunkEvents(t *testing.T) { + strPtr := func(s string) *string { return &s } + 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") + } +}