You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs(ai-chat): say what happens to a message that is not injected
The docs described a mid-turn message becoming the next turn only when there
were no more step boundaries, and the client-side lifecycle credited the
frontend with auto-sending it. Neither matched the behaviour: a message the
agent declines to inject is now held on the backend and answered as the next
turn, with no client re-send involved, and that covers an explicit
`shouldInject: false` as well as a turn that never reaches a boundary.
Also spells out that a declined message keeps its place in the queue, so it
survives a crash rather than living only in the worker that received it.
Copy file name to clipboardExpand all lines: docs/ai-chat/client-protocol.mdx
+4-1Lines changed: 4 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -953,9 +953,12 @@ You can send messages while the agent is still streaming a response. These are *
953
953
954
954
The wire format is identical to a normal `kind: "message"` send — same `.in` channel, single `message` field. The difference is timing. What happens depends on the agent's `pendingMessages` configuration:
955
955
956
-
- **With `pendingMessages.shouldInject`**: the message is injected into the model's context at the next `prepareStep` boundary. The agent sees it and can adjust its behavior mid-response.
956
+
- **With `pendingMessages.shouldInject` returning `true`**: the message is injected into the model's context at the next `prepareStep` boundary. The agent sees it and can adjust its behavior mid-response.
957
+
- **With a `pendingMessages` config that declines it**, either because `shouldInject` returned `false` or because it is absent: the message stays queued on the backend and is answered as the next turn.
957
958
- **Without `pendingMessages` config**: the message queues for the next turn.
958
959
960
+
In every case the message is answered. A declined message keeps its place in the queue, so it also survives a crash and is picked up by whichever run continues the conversation.
961
+
959
962
See [Pending Messages](/ai-chat/pending-messages) for how to configure the agent side.
Copy file name to clipboardExpand all lines: docs/ai-chat/pending-messages.mdx
+3-3Lines changed: 3 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -10,7 +10,7 @@ When an AI agent is executing tool calls, users may want to send a message that
10
10
11
11
By default (without `pendingMessages`), a message sent while the agent is responding never interrupts the in-flight response: it's buffered and processed as its own turn once the current turn completes, with multiple messages running sequentially in arrival order.
12
12
13
-
The `pendingMessages` option enables steering instead, injecting user messages between tool-call steps via the AI SDK's `prepareStep`. Messages that arrive during streaming are queued and injected at the next step boundary. If there are no more step boundaries (single-step response or final text generation), the message becomes the next turn automatically.
13
+
The `pendingMessages` option enables steering instead, injecting user messages between tool-call steps via the AI SDK's `prepareStep`. Messages that arrive during streaming are queued and injected at the next step boundary. A message that is not injected becomes the next turn instead, whether that is because `shouldInject` returned `false` or because there were no more step boundaries (single-step response or final text generation). Nothing is lost either way, and the backend handles it, so no client-side re-send is involved.
14
14
15
15
## How it works
16
16
@@ -20,7 +20,7 @@ The `pendingMessages` option enables steering instead, injecting user messages b
20
20
4. At the next `prepareStep` boundary (between tool-call steps), `shouldInject` is called
21
21
5. If it returns `true`, the message is injected into the LLM's context
22
22
6. A `data-pending-message-injected` stream chunk confirms injection to the frontend
23
-
7. If `prepareStep` never fires (no tool calls), the message becomes the next turn
23
+
7. If `shouldInject` returns `false`, or `prepareStep` never fires (no tool calls), the message stays queued on the backend and is answered as the next turn
-**Steering messages** are sent via `transport.sendPendingMessage()` immediately. They appear as purple pending bubbles. If injected, they disappear from the overlay and render inline at the injection point. If not injected (no more step boundaries), they auto-send as the next turn when the response finishes.
313
+
-**Steering messages** are sent via `transport.sendPendingMessage()` immediately. They appear as purple pending bubbles. If injected, they disappear from the overlay and render inline at the injection point. If not injected, the backend answers them as the next turn once the response finishes; the client does not need to re-send them.
314
314
315
315
-**Queued messages** stay client-side until the turn completes, then auto-send as the next turn via `sendMessage()`. They can be promoted to steering mid-stream by clicking "Steer instead".
|`shouldInject`|`(event: PendingMessagesBatchEvent) => boolean \| Promise<boolean>`| No | Decide whether to inject the batch between tool-call steps. If absent, no injection. |
399
+
|`shouldInject`|`(event: PendingMessagesBatchEvent) => boolean \| Promise<boolean>`| No | Decide whether to inject the batch between tool-call steps. If absent, nothing is injected and the messages are answered as the next turn.|
400
400
|`prepare`|`(event: PendingMessagesBatchEvent) => ModelMessage[] \| Promise<ModelMessage[]>`| No | Transform the batch before injection. Default: convert each via `convertToModelMessages`. |
401
401
|`onReceived`|`(event: PendingMessageReceivedEvent) => void \| Promise<void>`| No | Called when a message arrives during streaming (per-message). |
402
402
|`onInjected`|`(event: PendingMessagesInjectedEvent) => void \| Promise<void>`| No | Called after a batch is injected via prepareStep. |
0 commit comments