fix(backends,markdown): stream turn events live, no blank lines for tool blocks
This commit is contained in:
@@ -296,15 +296,21 @@ class ClaudeSdkBackend:
|
||||
live = await self._acquire(key, session_id=session_id, history=prior, spec=spec)
|
||||
message_id = f"msg_{uuid.uuid4().hex}"
|
||||
yield build_message_start(message_id=message_id, model=self._agent.model)
|
||||
turn = _Turn()
|
||||
async with live.lock:
|
||||
live.running_turn = turn_id or message_id
|
||||
live.last_used = time.monotonic()
|
||||
try:
|
||||
turn = await self._run_turn(live, prompt, observer=observer)
|
||||
# Events go out as the CLI produces them: the frontends
|
||||
# stream text and thinking live, the turn is not buffered.
|
||||
async for event in self._run_turn(live, prompt, turn, observer):
|
||||
yield event
|
||||
except Exception:
|
||||
live.running_turn = None
|
||||
await self._pool.close(key)
|
||||
if not (live.resumed and live.turns == 0):
|
||||
# A dead resume can be reseeded from history, but only
|
||||
# while nothing of this turn has reached the caller yet.
|
||||
if not (live.resumed and live.turns == 0) or turn.events:
|
||||
raise
|
||||
_log.exception(
|
||||
"resume of %s failed, reseeding from history", live.session_id
|
||||
@@ -312,11 +318,11 @@ class ClaudeSdkBackend:
|
||||
live = await self._acquire(
|
||||
key, session_id=None, history=prior, spec=spec
|
||||
)
|
||||
turn = _Turn()
|
||||
async with live.lock:
|
||||
live.running_turn = turn_id or message_id
|
||||
turn = await self._run_turn(live, prompt, observer=observer)
|
||||
for event in turn.events:
|
||||
yield event
|
||||
async for event in self._run_turn(live, prompt, turn, observer):
|
||||
yield event
|
||||
live.turns += 1
|
||||
live.last_used = time.monotonic()
|
||||
live.running_turn = None
|
||||
@@ -373,11 +379,16 @@ class ClaudeSdkBackend:
|
||||
self,
|
||||
live: Session,
|
||||
prompt: str,
|
||||
*,
|
||||
turn: _Turn,
|
||||
observer: Callable[[Any], None] | None = None,
|
||||
) -> _Turn:
|
||||
) -> AsyncIterator[MessageStreamEvent]:
|
||||
"""Run one prompt, yielding wire events as they arrive.
|
||||
|
||||
``turn`` is filled in place (result, synthesized history, count of
|
||||
events already yielded) so the caller can finish bookkeeping - and
|
||||
decide whether a retry is still possible - after a failure.
|
||||
"""
|
||||
streaming = self._agent.options.include_partial_messages
|
||||
turn = _Turn()
|
||||
raw: list[Any] = []
|
||||
next_index = 0
|
||||
offset = 0
|
||||
@@ -404,12 +415,16 @@ class ClaudeSdkBackend:
|
||||
if isinstance(index, int):
|
||||
next_index = max(next_index, offset + index + 1)
|
||||
if streaming:
|
||||
turn.events.extend(_emit_stream_event(event, offset + index))
|
||||
for out in _emit_stream_event(event, offset + index):
|
||||
turn.events += 1
|
||||
yield out
|
||||
elif isinstance(message, AssistantMessage):
|
||||
raw.append(message)
|
||||
if not streaming:
|
||||
for block in message.content:
|
||||
turn.events.extend(_emit_block(block, next_index))
|
||||
for out in _emit_block(block, next_index):
|
||||
turn.events += 1
|
||||
yield out
|
||||
next_index += 1
|
||||
elif isinstance(message, UserMessage):
|
||||
raw.append(message)
|
||||
@@ -423,11 +438,10 @@ class ClaudeSdkBackend:
|
||||
"turn: agent=%s session=%s events=%d synthesized=%d stop=%s",
|
||||
self._agent.name,
|
||||
live.session_id,
|
||||
len(turn.events),
|
||||
turn.events,
|
||||
len(turn.synthesized),
|
||||
turn.stop_reason,
|
||||
)
|
||||
return turn
|
||||
|
||||
async def _acquire(
|
||||
self,
|
||||
@@ -606,7 +620,8 @@ class _SessionSpec:
|
||||
|
||||
@dataclass
|
||||
class _Turn:
|
||||
events: list[Any] = field(default_factory=list)
|
||||
events: int = 0
|
||||
"""Wire events already yielded to the caller."""
|
||||
synthesized: list[dict[str, Any]] = field(default_factory=list)
|
||||
result: ResultMessage | None = None
|
||||
stop_reason: StopReason = "end_turn"
|
||||
|
||||
Reference in New Issue
Block a user