Skip to content

[Bug]: REASONING_MESSAGE_* corrupts an activity message that shares its id #2217

Description

@TheSeydiCharyyev

Pre-flight Checklist

  • I have searched existing issues and this hasn't been reported yet.
  • I am using the latest version AG-UI.

Describe the Bug

REASONING_MESSAGE_START, REASONING_MESSAGE_CONTENT and REASONING_MESSAGE_END in defaultApplyEvents match a message by id alone. When the id belongs to an activity message, the reasoning delta is appended to it: the activity message's structured content object is replaced by the string, so it is no longer a valid ActivityMessage, and no reasoning message is created at all.

This is the same class of bug as #1350, which covers the TEXT_MESSAGE_* handlers.

The reasoning handlers are inconsistent with themselves here. REASONING_ENCRYPTED_VALUE already guards against exactly this:

const message = messages.find((m) => m.id === entityId);
// Activity messages do not have encryptedValue
if (message?.role !== "activity" && message) {
  message.encryptedValue = encryptedValue;
  entityUpdated = true;
}

with a test to match — "should not set encryptedValue on activity messages" in default.reasoning.test.ts. So the intent is established: an activity message must not absorb reasoning data. START / CONTENT / END are missing that guard, and the damage there is worse — the encrypted-value path only skips setting a field, while the content path overwrites the whole content object.

ACTIVITY_DELTA and resolveOrCreateAssistantMessage (TOOL_CALL_START) apply the same role check elsewhere in the file.

Steps to Reproduce

Apply these events through defaultApplyEvents, with a reasoning message reusing the activity message's id:

{ type: EventType.ACTIVITY_SNAPSHOT, messageId: "shared-id", activityType: "PLAN", content: { tasks: ["step one"] } }
{ type: EventType.REASONING_MESSAGE_START, messageId: "shared-id" }
{ type: EventType.REASONING_MESSAGE_CONTENT, messageId: "shared-id", delta: "thinking..." }
{ type: EventType.REASONING_MESSAGE_END, messageId: "shared-id" }

Result on current main:

id=shared-id  role=activity  content="thinking..."
reasoning messages: 0

Expected Behavior

The activity message keeps its content object, and the reasoning handlers warn on the id collision and skip, the same way REASONING_ENCRYPTED_VALUE already does.

Environment

@ag-ui/client (sdks/typescript/packages/client/src/apply/default.ts)
main @ b646b463
Node 22.17.1, pnpm 10.33.4

Additional Context

I have the fix ready as a follow-up to #2201 (which does the same for TEXT_MESSAGE_*) and can open it once that one is reviewed, keeping them separate so each stays small.

Metadata

Metadata

Assignees

No one assigned

    Labels

    SDKbugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions