bug: chat.agent drops tool results from step 3 on when prepareStep returns messages (AI SDK 7)
I maintainer di solito rispondono entro 1 giorno
Nessuno ha ancora preso questa issue.
- #5101 di @cocvn157 — chiusa senza merge
Valutazione
- Difficoltà
- 3/5
- Tempo stimato
- 1-2 giorni
- Idoneità per principianti
- 58/100
- Tipo di issue
- Bug
- Chiarezza
- Specificata chiaramente
- Stato di attività
- Attiva
- Stack tecnologico
- node.js, typescript
Direzione di ricerca
Start in packages/trigger-sdk/src/v3/steeringContext.ts, especially retainStepMessages, and compare the prepareStep inputs and step response history for AI SDK 6 and 7. Reproduce with the linked trigger-repro script using three tool calls and a no-op prepareStep; add a regression test for the AI SDK 7 response shape. Done means each later prompt retains every earlier tool result on AI SDK 5, 6, and 7 without duplicating results.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
Provide environment information
System:
OS: Linux 7.2 CachyOS Linux
CPU: (16) x64 AMD Ryzen 7 4800H with Radeon Graphics
Memory: 2.75 GB / 15.04 GB
Container: Yes
Shell: 5.9.2 - /usr/bin/zsh
Binaries:
Node: 24.18.0 - /home/senpai/.local/share/nvm/v24.18.0/bin/node
npm: 12.0.1 - /home/senpai/.local/share/nvm/v24.18.0/bin/npm
pnpm: 11.13.1 - /home/senpai/.local/share/nvm/v24.18.0/bin/pnpm
bun: 1.3.14 - /home/senpai/.bun/bin/bun
Packages: @trigger.dev/[email protected], [email protected], [email protected].
Describe the bug
With AI SDK 7, if a chat.agent's streamText has a prepareStep that returns messages, the model stops receiving new tool results after the first one. From step 3 on, every prompt contains only the first tool call and its result. The model never sees its later results, so it repeats the same tool call until stopWhen ends the turn.
Even a no-op prepareStep: ({ messages }) => ({ messages }) triggers it. In our production agent this showed up as the model running the same shell command dozens of times in one turn.
Expected: each step's prompt includes every earlier tool call and result, the same as without prepareStep.
Cause: chat.agent wraps prepareStep in retainStepMessages (packages/trigger-sdk/src/v3/steeringContext.ts):
const responses = input.steps.at(-1)?.response.messages ?? [];
const messages = retained ? [...retained, ...responses.slice(responseCount)] : input.messages;
// ...
responseCount = responses.length;
The doc comment above it states the assumption that no longer holds:
The same logic works when the installed AI SDK already retains overrides: its input is not concatenated again.
That code was written for AI SDK 5/6, where step.response.messages holds every message so far, so slicing off the first responseCount leaves only the new ones. AI SDK 7 changed this (migration guide, "Step Response Messages Are No Longer Accumulated"): each step's response.messages holds only that step's messages. A tool step always yields 2 messages (tool call + tool result), so responseCount stays at 2, and from step 3 on responses.slice(2) is empty. Nothing new is added to retained, and the stale copy replaces the correct input.messages.
| Step | responses (AI SDK 7) |
responseCount |
Added to history |
|---|---|---|---|
| 2 | step 1's 2 messages | 0 | both ✓ |
| 3 | step 2's 2 messages | 2 | nothing ✗ |
| 4 | step 3's 2 messages | 2 | nothing ✗ |
All of these are needed to hit it:
ai7.x. With[email protected], the same code works.- Something returns
messagesfromprepareStep: either the user'sprepareStep, or the SDK's own built-in one (steering via pending messages, compaction,chat.inject). Until the first override, the wrapper passesinput.messagesthrough. - At least 3 tool steps in one turn. Step 2 is always correct.
Because of (2), this also affects agents without a user prepareStep: a single steering message injected mid-turn is enough. With no user prepareStep and one steering message on ai@7, steps 3 and 4 both see ["user:go", "result-1", "user:steer"] (result-2 and result-3 missing); on ai@6 the same run is correct.
Reproduction repo
https://github.com/iharsh02/BugsBunny/tree/02550d8/trigger-repro
To reproduce
git clone https://github.com/iharsh02/BugsBunny && cd BugsBunny/trigger-repro
npm ci
node repro.mjs control # no prepareStep
node repro.mjs # no-op prepareStep
repro.mjs uses only public API and needs no network or API keys. It runs a chat.agent through mockChatAgent from @trigger.dev/sdk/ai/test, with a scripted MockLanguageModelV3 that calls a lookup tool 3 times (its execute returns result-N) and then answers. Each model call logs the tool results in the prompt it actually received.
The core of it:
const agent = chat.agent({
id: "repro",
run: async ({ messages, signal, streamText }) =>
streamText({
model,
messages,
tools: { lookup },
stopWhen: isStepCount(8),
abortSignal: signal,
...(usePrepareStep && { prepareStep: ({ messages }) => ({ messages }) }),
}),
});
Output:
$ node repro.mjs control
model call 1 sees: []
model call 2 sees: [ 'result-1' ]
model call 3 sees: [ 'result-1', 'result-2' ]
model call 4 sees: [ 'result-1', 'result-2', 'result-3' ]
$ node repro.mjs
model call 1 sees: []
model call 2 sees: [ 'result-1' ]
model call 3 sees: [ 'result-1' ] <- result-2 missing
model call 4 sees: [ 'result-1' ] <- result-2 and result-3 missing
With npm i [email protected], both runs match.
Suggested fix
AI SDK 7 passes the accumulated history to prepareStep as responseMessages. AI SDK 5/6 don't have that field, so falling back keeps their behaviour unchanged:
type StepInput = {
messages: ModelMessage[];
steps: Array<{ response: { messages: ModelMessage[] } }>;
+ /** AI SDK 7+: response messages accumulated across all steps so far. */
+ responseMessages?: ModelMessage[];
};
- const responses = input.steps.at(-1)?.response.messages ?? [];
+ const responses = input.responseMessages ?? input.steps.at(-1)?.response.messages ?? [];
I applied this change to the built dist of 4.8.1 and the repro passes on [email protected], [email protected] and [email protected], including the steering case above.
The obvious alternative, dropping the slice ([...retained, ...responses]), fixes AI SDK 7 but duplicates tool results on AI SDK 5/6: step 3 sees ['result-1', 'result-1', 'result-2']. The SDK's peer range still includes ai 5 and 6, so that version isn't safe.
Why CI didn't catch it: the repo's dev dependency is ai ^6, so the suite runs on v6. The existing "SDK retains overrides" test feeds v6-style accumulated step responses together with v7's carry-forward of overrides, a combination no real AI SDK version produces. Happy to open a PR with the fix plus a v7-shaped regression test.
Additional information
With a real model. real-agent.mjs in the repro repo runs the same chat.agent with a real model (minimax/minimax-m3.1-flash via the AI Gateway). Its getCode tool returns a random code and takes the previous code as input, so the call's argument shows what the model thinks the last code was. The prompt is "Call getCode exactly 3 times, one call at a time. Then reply with only the 3 codes joined by dashes."
Without prepareStep:
model call 1 sees: []
getCode -> 5Y (previous: "")
model call 2 sees: [ '5Y' ]
getCode -> ZG (previous: "5Y")
model call 3 sees: [ '5Y', 'ZG' ]
getCode -> PE (previous: "ZG")
model call 4 sees: [ '5Y', 'ZG', 'PE' ]
answer: "5Y-ZG-PE" ✓
With prepareStep: ({ messages }) => ({ messages }):
model call 1 sees: []
getCode -> CP (previous: "")
model call 2 sees: [ 'CP' ]
getCode -> L2 (previous: "CP")
model call 3 sees: [ 'CP' ]
getCode -> KP (previous: "CP")
model call 4 sees: [ 'CP' ]
getCode -> 0B (previous: "CP")
...
model call 8 sees: [ 'CP' ]
getCode -> 8I (previous: "CP")
getCode calls: 8 (asked for 3), no answer — stopped by isStepCount(8)
The model keeps passing previous: "CP" because it never sees L2, KP and the later codes. That's the same loop we hit in production.
Affected versions. I reproduced this on @trigger.dev/sdk 4.8.1 with ai 7.0.153. We first hit it on 4.7.5 and also saw it on 4.7.7 and with ai 7.0.146. The code is unchanged on main as of fc33769a (2026-10-26).
Workaround until a fix ships. In dist/esm/v3/steeringContext.js and dist/commonjs/v3/steeringContext.js, change the const responses = ... line to const responses = input.responseMessages ?? input.steps.at(-1)?.response.messages ?? []; (the type addition only applies to the TS source). Or don't return messages from prepareStep (this doesn't help with SDK steering/compaction).
- Lingua principale
- TypeScript
- Stelle
- 16.5k
- Fork
- 1.5k
- Merge medio
- 1g 15h
- PR unite (30g)
- 17
Preparare l'ambiente
Questo progetto non fornisce container di sviluppo, Dockerfile né guida per i contributori, quindi l'ambiente è a tuo carico: parti dal suo README e consulta la nostra guida al primo contributo per i passaggi generali.
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di triggerdotdev/trigger.dev
-
bug: retry.fetch ignores the caller's abort signal and retries aborted requestsForse di nuovo libera Una pull request per questa issue è stata chiusa senza essere unita. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 76/100
triggerdotdev/trigger.dev#5116 · 2 commenti ·
I maintainer di solito rispondono entro 1 giorno
-
bug: Chat transport crashes on Safari < 17.4: AbortSignal.any is not a functionForse di nuovo libera Una pull request per questa issue è stata chiusa senza essere unita. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 83/100
triggerdotdev/trigger.dev#5098 · 2 commenti ·
I maintainer di solito rispondono entro 1 giorno
-
bug: Side menu items lose their active state on detail pagesForse di nuovo libera Una pull request per questa issue è stata chiusa senza essere unita. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
triggerdotdev/trigger.dev#5088 ·
I maintainer di solito rispondono entro 1 giorno
-
bug: RBAC loader logs "plugin found but failed to load" when @triggerdotdev/plugins is simply not installedForse di nuovo libera @itsfaryal l’ha presa 17 giorni fa e non c’è nessuna pull request aperta. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 82/100
triggerdotdev/trigger.dev#5083 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 83/100
triggerdotdev/trigger.dev#5072 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di triggerdotdev/trigger.dev
Issue simili
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
I maintainer di solito rispondono entro 1 giorno
-
bug platform-android
Difficoltà 2/5 1-3 ore Idoneità per principianti 68/100
Termix-SSH/Mobile#146 · 1 commento ·
I maintainer di solito rispondono entro 2 giorni
-
bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 66/100
devswha/herdr-web-ui#3875 ·
-
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 88/100
-
area: runtime chore cli consistency dependencies developer-experience difficulty: easy discovery documentation enhancement good first issue help wanted hygiene json-output packaging priority: P3 tests version
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
I maintainer di solito rispondono entro 1 giorno