Skip to content

Default message reducer overwrites text when OAuth resumes in same step #436

Description

@fplonka

eve version

eve@0.17.0

Node.js version

v24.17.0

Where does the bug occur?

Local dev (eve dev), Production (eve start / deployed)

Describe the bug

I have a chat app using eve, it has a connection to Outlook to read my email. When the model uses a tool which uses outlook it triggers an OAuth pop up. This works great, but there's a slight UI bug. I'm using useEveAgent and I render an entry in the chat UI to indicate that the auth pop up is pending. But then after I log in in the pop up, *the next agent message overwrites the message before the auth-indicator-entry instead of going after the auth-indicator-entry.

In eve events I see:

session.started

turn.started              turn_0 seq=0
message.received          turn_0 seq=0  "pls summarize my email from today"

step.started              turn_0 stepIndex=0 seq=0
reasoning.completed       turn_0 stepIndex=0 seq=0  "The user wants to summarize their emails from today. I need to use the Outlook connector to fetch today's emails. Let me search for the right tool to get emails."
message.completed         turn_0 stepIndex=0 seq=0  finishReason=tool-calls  "Sure! Let me search for the right Outlook tool to fetch today's emails."
actions.requested         turn_0 stepIndex=0 seq=0  connection_search {"keywords":"list messages inbox received today","connection":"outlook"}
action.result             turn_0 stepIndex=0 seq=0  connection_search completed
step.completed            turn_0 stepIndex=0 seq=0  finishReason=tool-calls

step.started              turn_0 stepIndex=1 seq=0
reasoning.completed       turn_0 stepIndex=1 seq=0  "I'll use `outlook__me_ListMessages` to list messages from today. I'll filter by today's date using an OData filter on `receivedDateTime`. Today is 2025-07-15. I'll fetch up to 50 messages, filtered to today, and select the key fields needed for a summary."
message.completed         turn_0 stepIndex=1 seq=0  finishReason=tool-calls  "I'll use `outlook__me_ListMessages` and filter for messages received today, selecting the key fields needed for a clean summary."
actions.requested         turn_0 stepIndex=1 seq=0  outlook__me_ListMessages {"filter":"receivedDateTime ge 2025-07-15T00:00:00Z and receivedDateTime lt 2025-07-16T00:00:00Z","orderby":["receivedDateTime desc"],"select":["subject","from","receivedDateTime","bodyPreview","isRead","importance"],"top":50}
step.completed            turn_0 stepIndex=1 seq=0  finishReason=tool-calls

authorization.required    turn_0 stepIndex=1 seq=0  name=outlook description="Authorization required for outlook"
authorization.completed   turn_0 stepIndex=1 seq=0  name=outlook outcome=authorized

step.started              turn_0 stepIndex=1 seq=0
reasoning.completed       turn_0 stepIndex=1 seq=0  "I found the `outlook__me_ListMessages` tool which lists messages in the user's mailbox. I'll use it to fetch today's emails with a filter for today's date. Let me use the receivedDateTime filter to get only today's messages. I need to know today's date first.\n\nToday's date - I'll use a filter like `receivedDateTime ge 2025-01-XX`. Let me get today's date."
message.completed         turn_0 stepIndex=1 seq=0  finishReason=tool-calls  "Got it. Now I'll fetch today's messages using `outlook__me_ListMessages` with a `receivedDateTime` filter for today. Let me first get today's date."
actions.requested         turn_0 stepIndex=1 seq=0  bash {"command":"date -u +%Y-%m-%d"}
action.result             turn_0 stepIndex=1 seq=0  bash completed stdout="2026-06-30\n"
step.completed            turn_0 stepIndex=1 seq=0  finishReason=tool-calls

But in the UI I see this

pls summarize my email from today

thinking

Sure! Let me search for the right Outlook tool to fetch today's emails.

connection_search keywords="list messages inbox received today" connection="outlook"

thinking

Got it. Now I'll fetch today's messages using outlook__me_ListMessages with a receivedDateTime filter for today. Let me first get today's date.

outlook__me_ListMessages filter="receivedDateTime ge 2025-07-15T00:00:0"… orderby=[1] select=[6] top=50

Sign-in · Outlook
Signed in.

bash command="date -u +%Y-%m-%d"

Note that the I'll use outlook__me_ListMessages to list messages from today. I'll filter... message isn't visible in the UI, and as the messages stream in I can see it being overwritten by the next Got it. Now I'll fetch today's messages using outlook__me_ListMessages... message.

Below is Codex's analysis of the issue, it looks reasonable to me. The message reducer should be adjusted to handle the events eve emits correctly or eve should give the event a new step index for the message after the oauth thing is done. Not sure what makes most sense for the project.


useEveAgent's default message reducer can drop an assistant text block when an OAuth authorization resumes in the same step.

The raw stream contains both assistant text blocks. The reduced data.messages projection only keeps the later one.

The likely cause is that text and reasoning parts are keyed only by stepIndex:

text:${part.stepIndex ?? 0}
reasoning:${part.stepIndex ?? 0}

If auth resumes with the same turnId and stepIndex, later text replaces earlier text.

Steps to reproduce

(Codex-written)

Observed with this stream shape:

message.completed        turn_0 stepIndex=1  "I'll use outlook..."
actions.requested        turn_0 stepIndex=1  outlook__me_ListMessages
authorization.required   turn_0 stepIndex=1
authorization.completed  turn_0 stepIndex=1
step.started             turn_0 stepIndex=1
message.completed        turn_0 stepIndex=1  "Got it. Now I'll fetch..."

The first message.completed is present in the raw event stream but missing from useEveAgent().data.messages.

A minimal test could feed these events through defaultMessageReducer() and assert that both text parts remain.

Expected behavior

(Codex-written)

Both assistant text blocks should remain in data.messages[].parts, in stream order.

Reasoning parts likely need the same treatment.

It may also be worth checking whether the runtime should advance stepIndex before resuming after auth. A second step.started with the same turnId and stepIndex is surprising. But the reducer should still tolerate existing streams with this shape.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions