Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 25 additions & 16 deletions src/agents/realtime/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -771,10 +771,22 @@ def _get_new_history(
)
if existing_index is not None:
new_history = old_history.copy()
if event.type == "message" and event.content is not None and len(event.content) > 0:
existing_item = old_history[existing_index]
if existing_item.type == "message":
# Merge content preserving existing transcript/text when incoming entry is empty
existing_item = old_history[existing_index]

# If it's a message, try to merge/preserve content while applying new status.
if event.type == "message" and existing_item.type == "message":
# Start with the incoming event but ensure we don't lose content if it's empty.
incoming_item = event
if not event.content:
# Preserve existing content but update status and other metadata.
incoming_item = existing_item.model_copy(
update={
"status": event.status,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Guard status access on empty-content message updates

In _get_new_history, the new empty-content path unconditionally reads event.status when event.type == "message". That works for AssistantMessageItem, but UserMessageItem/SystemMessageItem do not define a status field, so a sparse update such as an item_updated event with content=[] for an existing user/system item will raise AttributeError and break event processing. This is a regression from the previous behavior (which skipped empty-content message updates) and should be guarded with getattr(...) or restricted to assistant-role updates.

Useful? React with 👍 / 👎.

"previous_item_id": event.previous_item_id or existing_item.previous_item_id,
}
)
else:
# Specialized merge logic for non-empty content
if event.role == "assistant" and existing_item.role == "assistant":
assistant_existing_content = existing_item.content
assistant_incoming = event.content
Expand All @@ -799,10 +811,9 @@ def _get_new_history(
assistant_new_content.append(assistant_current)
else:
assistant_new_content.append(ac)
updated_assistant = event.model_copy(
incoming_item = event.model_copy(
update={"content": assistant_new_content}
)
new_history[existing_index] = updated_assistant
elif event.role == "user" and existing_item.role == "user":
user_existing_content = existing_item.content
user_incoming = event.content
Expand Down Expand Up @@ -860,8 +871,7 @@ def _image_url_str(val: object) -> str | None:
else:
merged.append(uc)

updated_user = event.model_copy(update={"content": merged})
new_history[existing_index] = updated_user
incoming_item = event.model_copy(update={"content": merged})
elif event.role == "system" and existing_item.role == "system":
system_existing_content = existing_item.content
system_incoming = event.content
Expand All @@ -877,14 +887,13 @@ def _image_url_str(val: object) -> str | None:
system_new_content.append(system_current)
else:
system_new_content.append(sc)
updated_system = event.model_copy(update={"content": system_new_content})
new_history[existing_index] = updated_system
else:
# Role changed or mismatched; just replace
new_history[existing_index] = event
else:
# If the existing item is not a message, just replace it.
new_history[existing_index] = event
incoming_item = event.model_copy(update={"content": system_new_content})

new_history[existing_index] = incoming_item
else:
# For non-messages (e.g. tool calls) or mismatches, just replace.
# This ensures status updates for tool calls are applied.
new_history[existing_index] = event
return new_history

# Otherwise, insert it after the previous_item_id if that is set
Expand Down
Loading