You do not need a live LLM to reproduce most bugs in an AI chat interface.
These failures are ordinary UI-state problems:
the first token arrives but the spinner remains;
a tool finishes but its card still says Running;
Stop aborts the request, yet a buffered token appears afterward;
retry duplicates the partial answer;
an error removes text the user already received.
Make the stream injectable, then test those event sequences deterministically.
Define an application-level event contract
type ChatEvent = | { type : ' token ' ; messageId : string ; value : string } | { type : ' tool.started ' ; id : string ; name : string } | { type : ' tool.completed ' ; id : string ; summary : string } | { type : ' completed ' ; messageId : string } | { type : ' failed ' ; messageId : string ; message : string }
type ChatStream = ( prompt : string , signal : AbortSignal , ) => AsyncIterable < ChatEvent >
Enter fullscreen mode
Exit fullscreen mode
The React component receives a ChatStream .…