Skip to content

Commit 472eaf4

Browse files
ericallamclaude
andcommitted
fix(chat): keep an instruction injected after an action for the next turn
An action and the real turn after it share a turn number. If the action's option build had already taken the instruction stash for that number, an instruction injected in between waited for the turn after. Anything injected since is now spliced into the stash when it is reused. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AxuSksX18bj1yhnLpkcQ6a
1 parent 6482650 commit 472eaf4

2 files changed

Lines changed: 7 additions & 1 deletion

File tree

‎.changeset/inject-system-to-instructions.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@
44

55
`chat.inject()` with `role: "system"` now works. It previously put the system message into the conversation, which AI SDK 7 rejects for every provider: the next turn died with a generic "An error occurred." and persisted an empty assistant message, so the agent looked like it had stopped answering. System-role context is now appended to the model's instructions, which is also the only way to inject context the agent treats as trusted.
66

7-
Two things to know. Instructions are delivered by `chat.toStreamTextOptions()`, so a `run()` that calls `streamText` without spreading it does not receive a system-role injection. The conversational lane has no such requirement. And an injection applies to the next turn only, rather than repeating on every turn that follows it. Every inference call in that turn sees it, so a `run()` that builds options more than once gets the same instructions each time.
7+
Two things to know. Instructions are delivered by `chat.toStreamTextOptions()`, so a `run()` that calls `streamText` without spreading it does not receive a system-role injection. The conversational lane has no such requirement. And an injection applies to the next turn only, rather than repeating on every turn that follows it. Every inference call in that turn sees it, so a `run()` that builds options more than once gets the same instructions each time. An instruction injected after an action has run, and before the next message, reaches that next turn rather than the one after it.

‎packages/trigger-sdk/src/v3/ai.ts‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4820,6 +4820,12 @@ function toStreamTextOptions(options?: ToStreamTextOptionsOptions): Record<strin
48204820
let injectedBlocks: SystemModelMessage[] = [];
48214821
if (consumedThisTurn && consumedThisTurn.turn === currentTurn) {
48224822
injectedBlocks = consumedThisTurn.blocks;
4823+
// Anything injected since the stash was taken joins it, so an instruction
4824+
// added after an action read the lane still reaches the real turn that
4825+
// shares the action's turn number, rather than the one after.
4826+
if (injectedInstructions && injectedInstructions.length > 0) {
4827+
injectedBlocks.push(...injectedInstructions.splice(0));
4828+
}
48234829
} else if (injectedInstructions && injectedInstructions.length > 0) {
48244830
injectedBlocks = injectedInstructions.splice(0);
48254831
if (currentTurn !== undefined) {

0 commit comments

Comments
 (0)