diff --git a/provider/aguiprovider/hosting_events.go b/provider/aguiprovider/hosting_events.go index f44a9472..d9644e87 100644 --- a/provider/aguiprovider/hosting_events.go +++ b/provider/aguiprovider/hosting_events.go @@ -201,6 +201,10 @@ func hasTextLikeContent(contents message.Contents) bool { if ok && shouldEmitDataContentAsText(dc) { return true } + uc, ok := c.(*message.URIContent) + if ok && uc.URI != "" { + return true + } } return false } @@ -249,6 +253,15 @@ func contentToEvents(content message.Content, messageID string) ([]aguiEvents.Ev // message ID. MEAI batches all results under a shared MessageId, which // collapses them in FE reconciliation when the same id is reused. return []aguiEvents.Event{aguiEvents.NewToolCallResultEvent("result-"+callID, callID, contentStr)}, nil + case *message.URIContent: + // AG-UI has no native reference/attachment event; surface the URI as + // text so it is not silently dropped (mirrors the DataContent fallback + // and the A2A hosting path, which never drop unknown content). Gemini, + // for example, emits URIContent for generated files. + if c.URI == "" { + return nil, nil + } + return []aguiEvents.Event{aguiEvents.NewTextMessageContentEvent(messageID, c.URI)}, nil case *message.DataContent: return dataContentToEvents(c, messageID) default: diff --git a/provider/aguiprovider/hosting_test.go b/provider/aguiprovider/hosting_test.go index a648d30a..b9b19978 100644 --- a/provider/aguiprovider/hosting_test.go +++ b/provider/aguiprovider/hosting_test.go @@ -617,3 +617,28 @@ func TestHandler_MidStreamError_ClientObservesErrorContent(t *testing.T) { t.Fatalf("expected client to observe an *message.ErrorContent from RUN_ERROR, got messages %#v", resp.Messages) } } + +func TestHandler_URIContentEmittedAsText(t *testing.T) { + a := newTestAgent(func(_ context.Context, _ []*message.Message, _ ...agent.Option) iter.Seq2[*agent.ResponseUpdate, error] { + return func(yield func(*agent.ResponseUpdate, error) bool) { + yield(&agent.ResponseUpdate{ + MessageID: "msg-1", + Role: message.RoleAssistant, + Contents: message.Contents{&message.URIContent{URI: "https://example.com/generated.png", MediaType: "image/png"}}, + }, nil) + } + }) + h := aguiprovider.NewJSONHTTPHandler(a, aguiprovider.HandlerConfig{}) + + body := `{"threadId":"thread-1","runId":"run-1","messages":[{"id":"u1","role":"user","content":"ping"}]}` + req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(body)) + rr := httptest.NewRecorder() + h.ServeHTTP(rr, req) + + if rr.Code != http.StatusOK { + t.Fatalf("status = %d, want %d", rr.Code, http.StatusOK) + } + if content := rr.Body.String(); !strings.Contains(content, "https://example.com/generated.png") { + t.Fatalf("expected URIContent URI surfaced in SSE payload, got %q", content) + } +}