@@ -128,19 +128,37 @@ async def receive_messages(self) -> AsyncIterator[Message]:
128128 if message :
129129 yield message
130130
131- async def send_message (self , content : str , session_id : str = "default" ) -> None :
132- """Send a new message in streaming mode."""
131+ async def send_message (self , prompt : str | AsyncIterable [dict [str , Any ]], session_id : str = "default" ) -> None :
132+ """
133+ Send a new message in streaming mode.
134+
135+ Args:
136+ prompt: Either a string message or an async iterable of message dictionaries
137+ session_id: Session identifier for the conversation
138+ """
133139 if not self ._transport :
134140 raise CLIConnectionError ("Not connected. Call connect() first." )
135141
136- message = {
137- "type" : "user" ,
138- "message" : {"role" : "user" , "content" : content },
139- "parent_tool_use_id" : None ,
140- "session_id" : session_id ,
141- }
142-
143- await self ._transport .send_request ([message ], {"session_id" : session_id })
142+ # Handle string prompts
143+ if isinstance (prompt , str ):
144+ message = {
145+ "type" : "user" ,
146+ "message" : {"role" : "user" , "content" : prompt },
147+ "parent_tool_use_id" : None ,
148+ "session_id" : session_id ,
149+ }
150+ await self ._transport .send_request ([message ], {"session_id" : session_id })
151+ else :
152+ # Handle AsyncIterable prompts
153+ messages = []
154+ async for msg in prompt :
155+ # Ensure session_id is set on each message
156+ if "session_id" not in msg :
157+ msg ["session_id" ] = session_id
158+ messages .append (msg )
159+
160+ if messages :
161+ await self ._transport .send_request (messages , {"session_id" : session_id })
144162
145163 async def interrupt (self ) -> None :
146164 """Send interrupt signal (only works with streaming mode)."""
@@ -150,19 +168,24 @@ async def interrupt(self) -> None:
150168
151169 async def receive_response (self ) -> AsyncIterator [Message ]:
152170 """
153- Receive messages from Claude until a ResultMessage is received .
171+ Receive messages from Claude until and including a ResultMessage .
154172
155- This is an async iterator that yields all messages including the final ResultMessage.
156- It's a convenience method over receive_messages() that automatically stops iteration
157- after receiving a ResultMessage.
173+ This async iterator yields all messages in sequence and automatically terminates
174+ after yielding a ResultMessage (which indicates the response is complete).
175+ It's a convenience method over receive_messages() for single-response workflows.
176+
177+ **Stopping Behavior:**
178+ - Yields each message as it's received
179+ - Terminates immediately after yielding a ResultMessage
180+ - The ResultMessage IS included in the yielded messages
181+ - If no ResultMessage is received, the iterator continues indefinitely
158182
159183 Yields:
160184 Message: Each message received (UserMessage, AssistantMessage, SystemMessage, ResultMessage)
161185
162186 Example:
163187 ```python
164188 async with ClaudeSDKClient() as client:
165- # Send message and process response
166189 await client.send_message("What's the capital of France?")
167190
168191 async for msg in client.receive_response():
@@ -172,14 +195,12 @@ async def receive_response(self) -> AsyncIterator[Message]:
172195 print(f"Claude: {block.text}")
173196 elif isinstance(msg, ResultMessage):
174197 print(f"Cost: ${msg.total_cost_usd:.4f}")
198+ # Iterator will terminate after this message
175199 ```
176200
177201 Note:
178- The iterator will automatically stop after yielding a ResultMessage.
179- If you need to collect all messages into a list, use:
180- ```python
181- messages = [msg async for msg in client.receive_response()]
182- ```
202+ To collect all messages: `messages = [msg async for msg in client.receive_response()]`
203+ The final message in the list will always be a ResultMessage.
183204 """
184205 async for message in self .receive_messages ():
185206 yield message
0 commit comments