@@ -183,47 +183,24 @@ class HookResolution(BaseEvent):
183183]
184184
185185
186- async def replay_message_events (
186+ async def _replay_message_events (
187187 msg : messages .Message ,
188188) -> AsyncGenerator [Event ]:
189- """Synthesize stream events for ``msg``.
190-
191- Use when you have a complete ``Message`` from a non-streaming source —
192- e.g., the result of a Temporal activity, a cached LLM response, or an
193- offline test fixture — and want to feed it through code that consumes
194- an async event stream (``ai.Stream``, ``ai.ToolRunner``, custom loops
195- that mirror the default loop's shape, etc.)::
196-
197- async with ai.Stream(ai.events.replay_message_events(msg)) as stream:
198- async with ai.ToolRunner() as tr:
199- async for event in ai.util.merge(stream, tr.events()):
200- ...
201-
202- Each part is emitted as the start/delta/end triple a streaming adapter
203- would have produced, in part order, bracketed by ``StreamStart`` and
204- ``StreamEnd``. The full body of text/reasoning/tool-args is sent as a
205- single delta — the granularity of the model's original chunking is
206- not recoverable from a complete message.
207-
208- Parts with no model-layer event analog — ``ToolResultPart``,
209- ``HookPart`` — are skipped silently; they are agent-layer concerns
210- and never appear on the model stream.
211- """
189+ """Synthesize stream events for ``msg``."""
190+ # See Stream.replay_message
212191 yield StreamStart ()
213192 for part in msg .parts :
214193 if isinstance (part , messages .TextPart ):
215194 yield TextStart (block_id = part .id )
216195 if part .text :
217196 yield TextDelta (block_id = part .id , chunk = part .text )
218- yield TextEnd (block_id = part .id )
197+ yield TextEnd (
198+ block_id = part .id , provider_metadata = part .provider_metadata
199+ )
219200 elif isinstance (part , messages .ReasoningPart ):
220201 yield ReasoningStart (block_id = part .id )
221202 if part .text :
222203 yield ReasoningDelta (block_id = part .id , chunk = part .text )
223- # Carry the signature (and any other reasoning metadata) on the
224- # end event, mirroring how the real adapters emit it -- otherwise
225- # a replayed-then-rebuilt turn loses its signature and can't be
226- # replayed to the provider.
227204 yield ReasoningEnd (
228205 block_id = part .id ,
229206 provider_metadata = part .provider_metadata ,
@@ -238,7 +215,11 @@ async def replay_message_events(
238215 tool_call_id = part .tool_call_id ,
239216 chunk = part .tool_args ,
240217 )
241- yield ToolEnd (tool_call_id = part .tool_call_id , tool_call = part )
218+ yield ToolEnd (
219+ tool_call_id = part .tool_call_id ,
220+ tool_call = part ,
221+ provider_metadata = part .provider_metadata ,
222+ )
242223 elif isinstance (part , messages .BuiltinToolCallPart ):
243224 yield BuiltinToolStart (
244225 tool_call_id = part .tool_call_id ,
@@ -249,7 +230,11 @@ async def replay_message_events(
249230 tool_call_id = part .tool_call_id ,
250231 chunk = part .tool_args ,
251232 )
252- yield BuiltinToolEnd (tool_call_id = part .tool_call_id , tool_call = part )
233+ yield BuiltinToolEnd (
234+ tool_call_id = part .tool_call_id ,
235+ tool_call = part ,
236+ provider_metadata = part .provider_metadata ,
237+ )
253238 elif isinstance (part , messages .BuiltinToolReturnPart ):
254239 yield BuiltinToolResult (tool_call_id = part .tool_call_id , result = part )
255240 elif isinstance (part , messages .FilePart ):
@@ -258,8 +243,9 @@ async def replay_message_events(
258243 data = part .data ,
259244 media_type = part .media_type ,
260245 filename = part .filename ,
246+ provider_metadata = part .provider_metadata ,
261247 )
262- yield StreamEnd ()
248+ yield StreamEnd (provider_metadata = msg . provider_metadata )
263249
264250
265251# ---------------------------------------------------------------------------
0 commit comments