Pre-flight Checklist
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.
Pre-flight Checklist
Describe the Bug
REASONING_MESSAGE_START,REASONING_MESSAGE_CONTENTandREASONING_MESSAGE_ENDindefaultApplyEventsmatch a message by id alone. When the id belongs to an activity message, the reasoning delta is appended to it: the activity message's structuredcontentobject is replaced by the string, so it is no longer a validActivityMessage, 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_VALUEalready guards against exactly this: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/ENDare missing that guard, and the damage there is worse — the encrypted-value path only skips setting a field, while the content path overwrites the wholecontentobject.ACTIVITY_DELTAandresolveOrCreateAssistantMessage(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:Result on current main:
Expected Behavior
The activity message keeps its
contentobject, and the reasoning handlers warn on the id collision and skip, the same wayREASONING_ENCRYPTED_VALUEalready does.Environment
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.