Skip to main content
Messaging apps have no streaming text box, so a streamed model answer is best sent as a few natural bubbles, each sent as soon as it is complete, with the typing indicator on in between.
TypeScript

What reply() does

conversation.reply(input) accepts a string, a piece of content, a list of content, or a stream. With a stream it:
  1. Turns the typing indicator on, and keeps it on (channels clear it after about 5 seconds).
  2. Reads text out of the stream as it arrives.
  3. Cuts a bubble at each natural break (below) and sends it while the model is still writing. Bubbles go out in order.
  4. Turns typing off when the stream ends, even if it failed.
It reads these streams as they are, with no adapter: Text goes out as markdown with fallback: "auto", so channels without formatting get clean plain text.

The bubble rule (for any language)

Without the SDK, apply this rule to the model’s text and send each bubble with POST /v1/conversations/{conversation_id}/messages, one after another, turning typing on (POST .../typing) before you start and every 4 seconds until you finish.
  1. A bubble ends at a paragraph break (a blank line), except after a lead-in line that ends with : and between items of one list, unless the bubble is already past the channel’s soft length.
  2. Past the soft length, a bubble ends at the next sentence end (. , ! , ? or … followed by a space).
  3. Never cut inside a fenced code block, unless the bubble would pass the channel’s hard limit; then cut at the last line break or space before it.
Use the event’s ID plus the bubble’s index as each send’s Idempotency-Key (evt_...:0, evt_...:1), so retrying a failed reply never sends a bubble twice.

Showing work while the agent thinks

For tool calls, retrieval or anything slow before the first word:
Messages in one conversation go out one at a time, in the order they were accepted, so bubbles never arrive out of order even when you send them quickly.