ChatController
Parameters
resolved on every turn, so switching provider/model applies to the next message without losing the conversation. It receives the controller's ToolApprover, which surfaces approval requests in the UI until respondToApproval is called. Threading: call it from one thread and give it a scope on a single-threaded dispatcher — viewModelScope (main thread) is the intended setup. The controller keeps its in-flight turn in plain fields, so a multi-threaded dispatcher such as Dispatchers.Default would race.
where turns run; collection happens on this scope's dispatcher (backends move their own I/O off the main thread).
streamed deltas are coalesced and published at most this often (one frame by default), so a model emitting hundreds of tokens per second never recomposes the UI per token. Structural events (tool calls, part ends, errors) publish immediately. 0 publishes every event.