Ch5/A.4: Convenience helper for callers that want both the yielded messages and the Terminal in one call. Drives the canonical :func:`query` async generator, collects all yielded messages into a list, and returns ``(messages, terminal)``. The terminal's reason discriminates why the
(
params: QueryParams,
)
| 1917 | |
| 1918 | |
| 1919 | async def run_query( |
| 1920 | params: QueryParams, |
| 1921 | ) -> tuple[list[Message | StreamEvent], Terminal]: |
| 1922 | """Ch5/A.4: Convenience helper for callers that want both the |
| 1923 | yielded messages and the Terminal in one call. |
| 1924 | |
| 1925 | Drives the canonical :func:`query` async generator, collects all |
| 1926 | yielded messages into a list, and returns ``(messages, terminal)``. |
| 1927 | The terminal's reason discriminates why the loop stopped (11 |
| 1928 | distinct reasons, matching TS query/transitions.ts). |
| 1929 | |
| 1930 | Tests and convenience entry points should use this helper. |
| 1931 | Streaming consumers (REPL, TUI) should keep using ``async for`` |
| 1932 | with their own ``TerminalHolder``. |
| 1933 | """ |
| 1934 | holder = TerminalHolder() |
| 1935 | messages: list[Message | StreamEvent] = [] |
| 1936 | async for msg in query(params, terminal_holder=holder): |
| 1937 | messages.append(msg) |
| 1938 | if holder.value is None: |
| 1939 | # Contract violation — the loop returned without setting |
| 1940 | # the terminal. Fall back to a model_error so callers don't |
| 1941 | # see ``None`` and crash. |
| 1942 | holder.value = Terminal( |
| 1943 | reason="model_error", |
| 1944 | error=RuntimeError("query() returned without setting Terminal"), |
| 1945 | ) |
| 1946 | return messages, holder.value |