Stamp buffered responses with a stable paragraph `item_id`. For events that are buffered (e.g. message chunks, reasoning), assign a stable paragraph `item_id` to resp.data.item_id so the frontend and storage layer can correlate incremental chunks with the final saved
(self, resp: BaseResponse)
| 105 | } |
| 106 | |
| 107 | def annotate(self, resp: BaseResponse) -> BaseResponse: |
| 108 | """Stamp buffered responses with a stable paragraph `item_id`. |
| 109 | |
| 110 | For events that are buffered (e.g. message chunks, reasoning), assign a |
| 111 | stable paragraph `item_id` to resp.data.item_id so the frontend and |
| 112 | storage layer can correlate incremental chunks with the final saved |
| 113 | conversation item. |
| 114 | |
| 115 | For REASONING events, if the caller has already set an item_id, it is |
| 116 | preserved to allow correlation of reasoning_started/reasoning/reasoning_completed. |
| 117 | MESSAGE_CHUNK events always use the buffer to get a stable paragraph item_id. |
| 118 | """ |
| 119 | data: UnifiedResponseData = resp.data |
| 120 | ev = resp.event |
| 121 | if ev in self._buffered_events: |
| 122 | # For REASONING events, trust the caller's item_id (set by orchestrator) |
| 123 | # and skip buffer-based id assignment. MESSAGE_CHUNK always uses buffer. |
| 124 | # TODO: consider when no item_id is set for REASONING, especially in remote agent calls |
| 125 | if ev == StreamResponseEvent.REASONING and data.item_id: |
| 126 | return resp |
| 127 | key: BufferKey = ( |
| 128 | data.conversation_id, |
| 129 | data.thread_id, |
| 130 | data.task_id, |
| 131 | ev, |
| 132 | ) |
| 133 | entry = self._buffers.get(key) |
| 134 | if not entry: |
| 135 | # Start a new paragraph buffer with a fresh paragraph item_id |
| 136 | entry = BufferEntry(role=data.role, agent_name=data.agent_name) |
| 137 | self._buffers[key] = entry |
| 138 | if entry.agent_name is None and data.agent_name: |
| 139 | entry.agent_name = data.agent_name |
| 140 | # Stamp the response with the stable paragraph id |
| 141 | data.item_id = entry.item_id |
| 142 | resp.data = data |
| 143 | return resp |
| 144 | |
| 145 | def ingest(self, resp: BaseResponse) -> List[SaveItem]: |
| 146 | """Ingest a response and return a list of SaveItem objects to persist. |