ChatController

constructor(backend: (ToolApprover) -> ChatBackend, scope: CoroutineScope, initialMessages: List<Message> = emptyList(), clock: () -> Long = System::currentTimeMillis, newId: () -> String = { UUID.randomUUID().toString() }, publishIntervalMs: Long = 32)(source)

Parameters

backend

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.

scope

where turns run; collection happens on this scope's dispatcher (backends move their own I/O off the main thread).

publishIntervalMs

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.