cloudflare / cloudflare/agents
Think sends “done” before saving the final transcript
- Dominant language
- TypeScript
- Stars
- 5.6k
- Forks
- 711
- Avg merge
- 1d 20h
- Merged PRs (30d)
- 53
Description
**Describe the bug**
`Think._streamResult()` sends the terminal `cf_agent_use_chat_response` frame with `done: true` before it persists the completed assistant message and broadcasts `cf_agent_chat_messages`.
`useAgentChat` therefore changes to `ready` before the authoritative transcript is available. If the client immediately sends another message, the later transcript broadcast can replace the local message array and temporarily remove the new optimistic user message.
**To Reproduce**
Steps to reproduce the behavior:
1. Create a Think chat using `useAgentChat`.
2. Send a message and wait for its response to finish.
3. Immediately call `sendMessage()` when the chat status changes to `ready`.
4. See the new user message appear optimistically.
5. See it disappear when the previous turn’s delayed `cf_agent_chat_messages` snapshot arrives.
6. See it return after the new turn finishes and another transcript snapshot is broadcast.
**Expected behavior**
Think should persist the completed assistant message and broadcast the authoritative transcript before sending the terminal `done: true` frame.
The completion order should be:
1. `_persistAssistantMessage()`
2. `_broadcastMessages()`
3. Broadcast `done: true`
After `useAgentChat` becomes ready, the client should be able to safely start another turn without a previous-turn snapshot replacing its optimistic message.
**Screenshots**
Not applicable.
**Version:**
Reproduced with `@cloudflare/think@0.15.0` and `agents@0.19.0`. The same ordering is present in `@cloudflare/think@0.15.1`.
**Additional context**
The WebSocket remains connected throughout the reproduction. This is separate from #1983, which concerns buffered messages during reconnect.
A regression test can drive `_streamResult()` with a completed UI message stream and assert the event order:
```text
persist → transcript broadcast → done
```
Moving persistence and _broadcastMessages() before the terminal frame resolves the issue without requiring client-side message reconciliation.
Contributor guide
Research direction
Start at Think._streamResult() and inspect its calls to _persistAssistantMessage() and _broadcastMessages(), along with the terminal done frame. Drive _streamResult() with a completed UI message stream and assert that the observed order is persist, transcript broadcast, then done. Done means the terminal frame is sent only after the transcript broadcast.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- backend-api-design
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 75/100