3737 SSEEventType ,
3838)
3939
40+ _TERMINAL_STATUSES = (
41+ SessionStatus .IDLE ,
42+ SessionStatus .TERMINATED ,
43+ SessionStatus .RESCHEDULED ,
44+ SessionStatus .DELETED ,
45+ )
46+
4047
4148class SessionEvents :
4249 """Session event send / list / stream."""
@@ -49,14 +56,7 @@ def send(
4956 session_id : str ,
5057 events : Sequence [Mapping [str , Any ]],
5158 ) -> Dict [str , Any ]:
52- """Send events to a session.
53-
54- Returns the server response dict::
55-
56- result = client.sessions.events.send(
57- session.id, [user_message("hello")],
58- )
59- """
59+ """Send events to a session."""
6060 if not events :
6161 raise ValueError ("events must contain at least 1 entry" )
6262 body = SessionEventSendParams (input = events ).to_dict ()
@@ -127,10 +127,7 @@ def stream(
127127
128128 ``event_deltas`` opts into incremental text streaming for the given
129129 event types (``"message"`` and/or ``"reasoning"``; aliases
130- ``"agent.message"`` / ``"agent.thinking"``). When set, the stream
131- additionally emits ``event_start`` / ``event_delta`` frames carrying
132- partial text — consume them via :attr:`_TypedEventStream.text_deltas`.
133- Other values are rejected by the server (HTTP 400).
130+ ``"agent.message"`` / ``"agent.thinking"``).
134131 """
135132 params : Optional [Dict [str , Any ]] = None
136133 if event_deltas :
@@ -154,6 +151,27 @@ class _TypedEventStream:
154151 def __init__ (self , stream : EventStream ) -> None :
155152 self ._stream = stream
156153
154+ @classmethod
155+ def from_raw_events (
156+ cls ,
157+ raw_events : Sequence [Mapping [str , Any ]],
158+ ) -> "_TypedEventStream" :
159+ """Create from a list of raw event dicts (for testing).
160+
161+ Events flow through the normal :func:`_coerce_event` pipeline.
162+ """
163+ obj = object .__new__ (cls )
164+
165+ class _RawStream :
166+ def __iter__ (self ):
167+ return iter (raw_events )
168+
169+ def close (self ):
170+ pass
171+
172+ obj ._stream = _RawStream ()
173+ return obj
174+
157175 def __enter__ (self ) -> "_TypedEventStream" :
158176 return self
159177
@@ -169,8 +187,7 @@ def text_stream(self):
169187 """Iterate over text chunks from agent messages.
170188
171189 Automatically stops when the session reaches ``idle`` or
172- ``terminated`` status, so callers don't need to handle
173- ``session_status`` events manually.
190+ ``terminated`` status.
174191 """
175192 for event in self :
176193 if getattr (event , "type" , None ) == SSEEventType .MESSAGE :
@@ -182,23 +199,15 @@ def text_stream(self):
182199 elif getattr (event , "type" , None ) == SSEEventType .SESSION_STATUS :
183200 block = event .content [0 ] if event .content else None
184201 d = getattr (block , "data" , None ) or {}
185- if d .get ("session_status" ) in (
186- SessionStatus .IDLE ,
187- SessionStatus .TERMINATED ,
188- SessionStatus .RESCHEDULED ,
189- SessionStatus .DELETED ,
190- ):
202+ if d .get ("session_status" ) in _TERMINAL_STATUSES :
191203 return
192204
193205 @property
194206 def text_deltas (self ):
195207 """Iterate over incremental text chunks from ``event_delta`` frames.
196208
197- Requires the stream to be opened with ``event_deltas`` (e.g.
198- ``event_deltas=["message"]``); otherwise the server emits only
199- terminal messages and this yields nothing (use
200- :attr:`text_stream` for terminal full text). Stops on terminal
201- ``session_status`` like :attr:`text_stream`.
209+ Requires the stream to be opened with ``event_deltas``; otherwise
210+ yields nothing (use :attr:`text_stream` for terminal full text).
202211 """
203212 for event in self :
204213 etype = getattr (event , "type" , None )
@@ -209,12 +218,7 @@ def text_deltas(self):
209218 elif etype == SSEEventType .SESSION_STATUS :
210219 block = event .content [0 ] if event .content else None
211220 d = getattr (block , "data" , None ) or {}
212- if d .get ("session_status" ) in (
213- SessionStatus .IDLE ,
214- SessionStatus .TERMINATED ,
215- SessionStatus .RESCHEDULED ,
216- SessionStatus .DELETED ,
217- ):
221+ if d .get ("session_status" ) in _TERMINAL_STATUSES :
218222 return
219223
220224 def close (self ) -> None :
@@ -232,14 +236,6 @@ async def send(
232236 session_id : str ,
233237 events : Sequence [Mapping [str , Any ]],
234238 ) -> Dict [str , Any ]:
235- """Send events to a session.
236-
237- Returns the server response dict::
238-
239- result = await client.sessions.events.send(
240- session.id, [user_message("hello")],
241- )
242- """
243239 if not events :
244240 raise ValueError ("events must contain at least 1 entry" )
245241 body = SessionEventSendParams (input = events ).to_dict ()
@@ -309,11 +305,6 @@ async def stream(
309305 event_deltas : Optional [Sequence [str ]] = None ,
310306 timeout : Optional [float ] = None ,
311307 ) -> "_AioTypedEventStream" :
312- """Open the SSE stream and return an async iterator of typed events.
313-
314- See :meth:`SessionEvents.stream` for the ``event_deltas`` opt-in
315- for incremental text streaming.
316- """
317308 params : Optional [Dict [str , Any ]] = None
318309 if event_deltas :
319310 params = {"event_deltas[]" : list (event_deltas )}
@@ -349,16 +340,9 @@ async def _aiter(self) -> AsyncIterator[ServerEvent]:
349340
350341 @property
351342 def text_stream (self ):
352- """Async iterator over text chunks from agent messages."""
353343 return self ._text_stream ()
354344
355345 async def _text_stream (self ):
356- """Async iterator over text chunks from agent messages.
357-
358- Automatically stops when the session reaches ``idle`` or
359- ``terminated`` status, so callers don't need to handle
360- ``session_status`` events manually.
361- """
362346 async for event in self :
363347 if getattr (event , "type" , None ) == SSEEventType .MESSAGE :
364348 for block in event .content or []:
@@ -369,21 +353,11 @@ async def _text_stream(self):
369353 elif getattr (event , "type" , None ) == SSEEventType .SESSION_STATUS :
370354 block = event .content [0 ] if event .content else None
371355 d = getattr (block , "data" , None ) or {}
372- if d .get ("session_status" ) in (
373- SessionStatus .IDLE ,
374- SessionStatus .TERMINATED ,
375- SessionStatus .RESCHEDULED ,
376- SessionStatus .DELETED ,
377- ):
356+ if d .get ("session_status" ) in _TERMINAL_STATUSES :
378357 return
379358
380359 @property
381360 def text_deltas (self ):
382- """Async iterator over incremental text from ``event_delta`` frames.
383-
384- Requires the stream to be opened with ``event_deltas``; otherwise
385- yields nothing (use :attr:`text_stream` for terminal full text).
386- """
387361 return self ._text_deltas ()
388362
389363 async def _text_deltas (self ):
@@ -396,12 +370,7 @@ async def _text_deltas(self):
396370 elif etype == SSEEventType .SESSION_STATUS :
397371 block = event .content [0 ] if event .content else None
398372 d = getattr (block , "data" , None ) or {}
399- if d .get ("session_status" ) in (
400- SessionStatus .IDLE ,
401- SessionStatus .TERMINATED ,
402- SessionStatus .RESCHEDULED ,
403- SessionStatus .DELETED ,
404- ):
373+ if d .get ("session_status" ) in _TERMINAL_STATUSES :
405374 return
406375
407376 async def aclose (self ) -> None :
0 commit comments