A 5e storytelling engine with an LLM DM

Dispatch tool calls as their fragments complete, not after the round master

Dm::turn drained the whole response, then dispatched every tool call at once: the player watched static narration while the model generated JSON for several calls, then all the results appeared together. Tool-call fragments arrive in non-decreasing index order over the wire, so a call is complete the moment a fragment for a higher index arrives, and can roll and reach the screen while the model still generates the next one. ChatStream's iterator now yields StreamItem::Text and StreamItem::Call as each completes, replacing the old text-only iterator plus into_tool_calls collected after the stream ended. Dm::turn dispatches a Call item immediately on a normal round, and still assembles the same assistant-then-tool-results messages and the same request wire format once the round finishes; a withheld round drops any Call it sees, unchanged from before. Request/response shape, history assembly, and cancel semantics are unchanged; only the timing of dispatch and display moves earlier. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CUXjWo1zGFhdJUig1hcQGf


+287 -111
4 changed files