Sync: response.messages() forces execution if not drained, so callers
no longer have to remember to call .text() first. Async: `await
response.messages()` awaits the force.
Internal sync paths (_response_to_dict, _chain_for_tool_results,
_build_full_chain, Response.reply, AsyncResponse.reply) use a new
private _messages_now() helper that assumes the response is already
drained, so they don't have to await on async responses.
Drops the now-obsolete "accessing .messages on un-awaited
AsyncResponse raises" parity test — that constraint goes away with
the method form.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Zero-arg sugar: when a response made tool calls and tool_results= is
not passed, reply() runs self.execute_tool_calls() and threads the
results into the next turn. Pass tool_results= explicitly to skip
the auto-execute path (e.g. for mutated or synthetic results). Also
forwards self.prompt.tools to the next turn so the model can call
the same tools again, mirroring Conversation.prompt's tools-or-self
rule.
AsyncResponse.reply() is now an awaitable coroutine — `await
response.reply(...)` — so the auto-execute path can `await
self.execute_tool_calls()` internally. This is a non-shipped API
break: existing async-reply callers in the test suite updated.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New tests/test_async_parity.py (18 tests) exercises every new API on
the async path via llm-echo (+ async_mock_model where relevant):
- AsyncResponse.to_dict() captures chain, output, datetime_utc
- AsyncResponse.to_dict() raises before await (guard parity)
- AsyncResponse.from_dict() rehydrates and matches original
- AsyncResponse.from_dict() + reply() continues correctly
- model= override on AsyncResponse.from_dict
- AsyncResponse.from_row fallback (SQLite rehydrate) populates
response.messages from _chunks so llm -c --async preserves the
assistant turn
- load_conversation(async_=True).prompt(...) builds full chain
- AsyncConversation.chain tool-result turn pre-bakes chain
- astream_events matches stream_events for text-only output
- reply chains across 3 async turns
- Full three-turn save→restore→reply loop under async
- reply(messages=[...]) kwarg appends to async chain
- response.messages raises on un-awaited AsyncResponse
- usage round-trips through async to_dict/from_dict
- sync/async structurally identical output for same prompts
Plus one test asserting Echo + EchoAsync are both registered.
The tests all passed on first run — Phase 7 async implementation was
already correct. These pin the invariants against future regressions.
620 tests pass (602 before + 18 new).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>