Skip to content

Commit 1fe685c

Browse files
committed
fix(sdk): mirror success-path accumulation on chat.agent error path
Replace a same-id continuation partial in place instead of dropping it as a dup, commit the errored user message unconditionally so it reaches the next live turn, and push only the appended partial's model messages to preserve a prior turn's compaction (reconvert only when replacing or folding).
1 parent dc63a6e commit 1fe685c

1 file changed

Lines changed: 41 additions & 18 deletions

File tree

  • packages/trigger-sdk/src/v3

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

Lines changed: 41 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7930,32 +7930,55 @@ function chatAgent<
79307930
capturedPartialResponse ??
79317931
((await assemblePartialFromChunks(turnBufferedChunks)) as TUIMessage | undefined);
79327932

7933-
// Include the partial in the UI-message views too, so customers who
7934-
// persist from uiMessages / newUIMessages (not responseMessage) keep
7935-
// it as well. Dedup by id against the accumulator to be safe.
7933+
// Build the complete error UI state. A HITL/tool continuation partial
7934+
// reuses an existing assistant id, so replace it in place (like the
7935+
// success path) instead of dropping it as a dup; otherwise append.
7936+
// `erroredWireMessage` was already folded into `erroredUIMessages`
7937+
// above when the pre-run merge hadn't happened.
7938+
const partialIdx =
7939+
partialResponse?.id != null
7940+
? erroredUIMessages.findIndex((m) => m.id === partialResponse!.id)
7941+
: -1;
7942+
const erroredUIMessagesWithPartial: TUIMessage[] = !partialResponse
7943+
? erroredUIMessages
7944+
: partialIdx === -1
7945+
? [...erroredUIMessages, partialResponse]
7946+
: (erroredUIMessages.map((m, i) =>
7947+
i === partialIdx ? partialResponse : m
7948+
) as TUIMessage[]);
7949+
79367950
const erroredNewUIMessages: TUIMessage[] = erroredWireMessage
79377951
? [erroredWireMessage]
79387952
: [];
79397953
if (partialResponse) {
79407954
erroredNewUIMessages.push(partialResponse);
79417955
}
7942-
const erroredUIMessagesWithPartial =
7943-
partialResponse && !erroredUIMessages.some((m) => m.id === partialResponse.id)
7944-
? [...erroredUIMessages, partialResponse]
7945-
: erroredUIMessages;
7946-
7947-
// Commit the recovered partial to the canonical accumulator so the
7948-
// partial survives past this hook: the run stays alive after an
7949-
// error, so the next turn sees it, and the error-path snapshot below
7950-
// (the recovery source for non-hydrate apps) persists it for reboot.
7951-
// Matches the success path, which accumulates the response. Guard the
7952-
// model-message conversion so a secondary failure here can't crash
7953-
// the still-alive run.
7954-
if (partialResponse) {
7955-
accumulatedUIMessages = erroredUIMessagesWithPartial as TUIMessage[];
7956+
7957+
// Commit the complete error state to the canonical accumulator so the
7958+
// errored user message and any recovered partial survive past this
7959+
// hook: the run stays alive after an error, so the next turn sees
7960+
// them, and the error-path snapshot below (the recovery source for
7961+
// non-hydrate apps) persists them for reboot. Matches the success
7962+
// path. Skip when nothing changed so an errored turn never needlessly
7963+
// reconverts. When the only change is an appended partial, push just
7964+
// its model messages to preserve a prior turn's compaction (mirrors
7965+
// the success path's append branch); otherwise reconvert from UI.
7966+
// Guard the conversion so a secondary failure can't crash the run.
7967+
if (erroredUIMessagesWithPartial !== accumulatedUIMessages) {
7968+
const onlyAppendedPartial =
7969+
partialResponse != null &&
7970+
partialIdx === -1 &&
7971+
erroredUIMessages === accumulatedUIMessages;
7972+
accumulatedUIMessages = erroredUIMessagesWithPartial;
79567973
locals.set(chatCurrentUIMessagesKey, accumulatedUIMessages);
79577974
try {
7958-
accumulatedMessages = await toModelMessages(accumulatedUIMessages);
7975+
if (onlyAppendedPartial) {
7976+
accumulatedMessages.push(
7977+
...(await toModelMessages([stripProviderMetadata(partialResponse!)]))
7978+
);
7979+
} else {
7980+
accumulatedMessages = await toModelMessages(accumulatedUIMessages);
7981+
}
79597982
} catch {
79607983
// Keep the prior model accumulator if conversion fails.
79617984
}

0 commit comments

Comments
 (0)