@trigger.dev/sdk 0.0.0-chat-prerelease-20260502065709 → 0.0.0-chat-prerelease-20260505140031
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commonjs/v3/ai-shared.d.ts +59 -2
- package/dist/commonjs/v3/ai.js +227 -14
- package/dist/commonjs/v3/ai.js.map +1 -1
- package/dist/commonjs/v3/chat-client.d.ts +35 -1
- package/dist/commonjs/v3/chat-client.js +30 -0
- package/dist/commonjs/v3/chat-client.js.map +1 -1
- package/dist/commonjs/v3/chat-server.d.ts +191 -0
- package/dist/commonjs/v3/chat-server.js +678 -0
- package/dist/commonjs/v3/chat-server.js.map +1 -0
- package/dist/commonjs/v3/chat-server.test.d.ts +1 -0
- package/dist/commonjs/v3/chat-server.test.js +515 -0
- package/dist/commonjs/v3/chat-server.test.js.map +1 -0
- package/dist/commonjs/v3/chat.d.ts +37 -0
- package/dist/commonjs/v3/chat.js +185 -0
- package/dist/commonjs/v3/chat.js.map +1 -1
- package/dist/commonjs/v3/chat.test.js +206 -0
- package/dist/commonjs/v3/chat.test.js.map +1 -1
- package/dist/commonjs/v3/sessions.js +2 -1
- package/dist/commonjs/v3/sessions.js.map +1 -1
- package/dist/commonjs/v3/test/mock-chat-agent.d.ts +32 -0
- package/dist/commonjs/v3/test/mock-chat-agent.js +34 -7
- package/dist/commonjs/v3/test/mock-chat-agent.js.map +1 -1
- package/dist/commonjs/v3/test/test-session-handle.d.ts +10 -4
- package/dist/commonjs/v3/test/test-session-handle.js +56 -5
- package/dist/commonjs/v3/test/test-session-handle.js.map +1 -1
- package/dist/commonjs/version.js +1 -1
- package/dist/esm/v3/ai-shared.d.ts +59 -2
- package/dist/esm/v3/ai.js +227 -14
- package/dist/esm/v3/ai.js.map +1 -1
- package/dist/esm/v3/chat-client.d.ts +35 -1
- package/dist/esm/v3/chat-client.js +30 -0
- package/dist/esm/v3/chat-client.js.map +1 -1
- package/dist/esm/v3/chat-server.d.ts +191 -0
- package/dist/esm/v3/chat-server.js +675 -0
- package/dist/esm/v3/chat-server.js.map +1 -0
- package/dist/esm/v3/chat-server.test.d.ts +1 -0
- package/dist/esm/v3/chat-server.test.js +513 -0
- package/dist/esm/v3/chat-server.test.js.map +1 -0
- package/dist/esm/v3/chat.d.ts +37 -0
- package/dist/esm/v3/chat.js +185 -0
- package/dist/esm/v3/chat.js.map +1 -1
- package/dist/esm/v3/chat.test.js +206 -0
- package/dist/esm/v3/chat.test.js.map +1 -1
- package/dist/esm/v3/sessions.js +2 -1
- package/dist/esm/v3/sessions.js.map +1 -1
- package/dist/esm/v3/test/mock-chat-agent.d.ts +32 -0
- package/dist/esm/v3/test/mock-chat-agent.js +34 -7
- package/dist/esm/v3/test/mock-chat-agent.js.map +1 -1
- package/dist/esm/v3/test/test-session-handle.d.ts +10 -4
- package/dist/esm/v3/test/test-session-handle.js +57 -6
- package/dist/esm/v3/test/test-session-handle.js.map +1 -1
- package/dist/esm/version.js +1 -1
- package/package.json +18 -3
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* import from `ai.ts`.
|
|
16
16
|
*/
|
|
17
17
|
import type { Task, AnyTask } from "@trigger.dev/core/v3";
|
|
18
|
-
import type { UIMessage } from "ai";
|
|
18
|
+
import type { ModelMessage, UIMessage } from "ai";
|
|
19
19
|
/**
|
|
20
20
|
* Message-part `type` value for the pending-message data part the agent
|
|
21
21
|
* injects when a follow-up message arrives mid-turn.
|
|
@@ -28,7 +28,16 @@ export declare const PENDING_MESSAGE_INJECTED_TYPE: "data-pending-message-inject
|
|
|
28
28
|
export type ChatTaskWirePayload<TMessage extends UIMessage = UIMessage, TMetadata = unknown> = {
|
|
29
29
|
messages: TMessage[];
|
|
30
30
|
chatId: string;
|
|
31
|
-
trigger: "submit-message" | "regenerate-message" | "preload" | "close" | "action"
|
|
31
|
+
trigger: "submit-message" | "regenerate-message" | "preload" | "close" | "action"
|
|
32
|
+
/**
|
|
33
|
+
* The customer's `chat.handover` route handler kicked us off in
|
|
34
|
+
* parallel with the first-turn `streamText` running in the warm
|
|
35
|
+
* Next.js process. The run sits idle on `session.in` waiting for
|
|
36
|
+
* a `kind: "handover"` (continue from tool execution) or
|
|
37
|
+
* `kind: "handover-skip"` (handler finished pure-text, exit
|
|
38
|
+
* cleanly). See `chat.handover` in `@trigger.dev/sdk/chat-server`.
|
|
39
|
+
*/
|
|
40
|
+
| "handover-prepare";
|
|
32
41
|
messageId?: string;
|
|
33
42
|
metadata?: TMetadata;
|
|
34
43
|
/** Custom action payload when `trigger` is `"action"`. Validated against `actionSchema` on the backend. */
|
|
@@ -77,6 +86,54 @@ export type ChatInputChunk<TMessage extends UIMessage = UIMessage, TMetadata = u
|
|
|
77
86
|
kind: "stop";
|
|
78
87
|
/** Optional human-readable reason. Maps to the legacy `chat-stop` record. */
|
|
79
88
|
message?: string;
|
|
89
|
+
} | {
|
|
90
|
+
/**
|
|
91
|
+
* Sent by `chat.headStart` when the customer's first-turn
|
|
92
|
+
* `streamText` finishes. The agent run (currently parked in
|
|
93
|
+
* `handover-prepare`) wakes, seeds its accumulators with
|
|
94
|
+
* `partialAssistantMessage`, and runs the normal turn loop
|
|
95
|
+
* (`onChatStart` → `onTurnStart` → … → `onTurnComplete`).
|
|
96
|
+
*
|
|
97
|
+
* What happens after that depends on `isFinal`:
|
|
98
|
+
*
|
|
99
|
+
* - `isFinal: false` — step 1 ended with `finishReason:
|
|
100
|
+
* "tool-calls"`. The partial carries the assistant's
|
|
101
|
+
* tool-call(s) wrapped in AI SDK's tool-approval round. The
|
|
102
|
+
* agent's `streamText` runs the approved tools and continues
|
|
103
|
+
* from step 2.
|
|
104
|
+
* - `isFinal: true` — step 1 ended pure-text (no tool calls).
|
|
105
|
+
* The partial carries the final assistant text. The agent
|
|
106
|
+
* skips the LLM call entirely (the response is already
|
|
107
|
+
* complete on the customer side) and runs `onTurnComplete`
|
|
108
|
+
* with the partial as `responseMessage` so persistence and
|
|
109
|
+
* any post-turn work fire normally.
|
|
110
|
+
*/
|
|
111
|
+
kind: "handover";
|
|
112
|
+
/** Customer's step-1 response messages (ModelMessage form). */
|
|
113
|
+
partialAssistantMessage: ModelMessage[];
|
|
114
|
+
/**
|
|
115
|
+
* The UI messageId the customer's handler used for its step-1
|
|
116
|
+
* assistant message. The agent reuses this so any post-handover
|
|
117
|
+
* chunks (tool-output-available, step-2 text, data-* parts
|
|
118
|
+
* written by hooks) merge into the SAME assistant message on
|
|
119
|
+
* the browser side instead of starting a new one.
|
|
120
|
+
*/
|
|
121
|
+
messageId?: string;
|
|
122
|
+
/**
|
|
123
|
+
* Whether the customer's step 1 is the final response. See
|
|
124
|
+
* `kind` description above for the two branches.
|
|
125
|
+
*/
|
|
126
|
+
isFinal: boolean;
|
|
127
|
+
} | {
|
|
128
|
+
/**
|
|
129
|
+
* Sent by `chat.headStart` only when the customer's handler
|
|
130
|
+
* ABORTS before producing a finishReason (e.g., dispatch error,
|
|
131
|
+
* stream cancelled before any tokens). The agent run exits
|
|
132
|
+
* cleanly without firing turn hooks. Normal pure-text and
|
|
133
|
+
* tool-call finishes go through `kind: "handover"` with the
|
|
134
|
+
* appropriate `isFinal` flag.
|
|
135
|
+
*/
|
|
136
|
+
kind: "handover-skip";
|
|
80
137
|
};
|
|
81
138
|
/**
|
|
82
139
|
* Extracts the client-data (`metadata`) type from a chat task.
|
package/dist/commonjs/v3/ai.js
CHANGED
|
@@ -672,12 +672,113 @@ const stopInput = {
|
|
|
672
672
|
await getChatSession().in.send({ kind: "stop", message: data?.message }, options?.requestOptions);
|
|
673
673
|
},
|
|
674
674
|
};
|
|
675
|
+
/**
|
|
676
|
+
* Internal facade for waiting on the handover signal. Mirrors
|
|
677
|
+
* `messagesInput` / `stopInput` so the wait paths and tracing
|
|
678
|
+
* attributes stay consistent across all input-stream branches.
|
|
679
|
+
* @internal
|
|
680
|
+
*/
|
|
681
|
+
const handoverInput = {
|
|
682
|
+
async waitWithIdleTimeout(options) {
|
|
683
|
+
while (true) {
|
|
684
|
+
const result = await getChatSession().in.waitWithIdleTimeout(options);
|
|
685
|
+
if (!result.ok)
|
|
686
|
+
return result;
|
|
687
|
+
if (result.output.kind === "handover" ||
|
|
688
|
+
result.output.kind === "handover-skip") {
|
|
689
|
+
return { ok: true, output: result.output };
|
|
690
|
+
}
|
|
691
|
+
// Other kinds (message, stop) are not expected during handover-prepare.
|
|
692
|
+
// Loop back; the message and stop facades have their own listeners
|
|
693
|
+
// running so signals on those kinds aren't lost.
|
|
694
|
+
}
|
|
695
|
+
},
|
|
696
|
+
};
|
|
675
697
|
/**
|
|
676
698
|
* Per-turn deferred promises. Registered via `chat.defer()`, awaited
|
|
677
699
|
* before `onTurnComplete` fires. Reset each turn.
|
|
678
700
|
* @internal
|
|
679
701
|
*/
|
|
680
702
|
const chatDeferKey = locals_js_1.locals.create("chat.defer");
|
|
703
|
+
/**
|
|
704
|
+
* Run-scoped slot holding the partial assistant message handed over by
|
|
705
|
+
* `chat.handover` from a customer's first-turn `streamText`. Appended
|
|
706
|
+
* to `accumulatedMessages` during turn 0 setup so `streamText` resumes
|
|
707
|
+
* at tool execution. Cleared (read once) after consumption.
|
|
708
|
+
* @internal
|
|
709
|
+
*/
|
|
710
|
+
const chatHandoverPartialKey = locals_js_1.locals.create("chat.handoverPartial");
|
|
711
|
+
/**
|
|
712
|
+
* Run-scoped slot holding the assistant `messageId` the customer's
|
|
713
|
+
* `chat.handover` handler used for its step-1 stream. The agent reuses
|
|
714
|
+
* it on the agent-side `toUIMessageStream` (and the synthesized
|
|
715
|
+
* partial UIMessage in `originalMessages`) so all chunks merge into a
|
|
716
|
+
* single assistant message on the browser side.
|
|
717
|
+
* @internal
|
|
718
|
+
*/
|
|
719
|
+
const chatHandoverMessageIdKey = locals_js_1.locals.create("chat.handoverMessageId");
|
|
720
|
+
/**
|
|
721
|
+
* Run-scoped slot indicating that the customer's step-1 head-start
|
|
722
|
+
* response is the FINAL turn response. When true, turn 0 runs through
|
|
723
|
+
* the full turn-loop hooks but SKIPS the `userRun` / `streamText`
|
|
724
|
+
* call — the customer's partial already IS the response. The agent's
|
|
725
|
+
* `onTurnComplete` fires with that partial so persistence + any
|
|
726
|
+
* post-turn work happens normally. Cleared after consumption.
|
|
727
|
+
* @internal
|
|
728
|
+
*/
|
|
729
|
+
const chatHandoverIsFinalKey = locals_js_1.locals.create("chat.handoverIsFinal");
|
|
730
|
+
/**
|
|
731
|
+
* Build a UIMessage representation of a `chat.handover` partial so AI
|
|
732
|
+
* SDK's `processUIMessageStream` can transition `tool-output-available`
|
|
733
|
+
* chunks (emitted by the initial-tool-execution branch when the
|
|
734
|
+
* approval round runs) onto the existing tool-call. Without this,
|
|
735
|
+
* `state.message.parts` is empty when the agent's `streamText`
|
|
736
|
+
* finishes, and AI SDK throws
|
|
737
|
+
* `UIMessageStreamError: No tool invocation found`.
|
|
738
|
+
*
|
|
739
|
+
* Only the assistant message matters — the synthesized
|
|
740
|
+
* `tool-approval-response` rows are AI-SDK-internal and don't need a
|
|
741
|
+
* UIMessage representation. We map:
|
|
742
|
+
* - `text` parts → `{ type: "text", text }`
|
|
743
|
+
* - `tool-call` parts → `{ type: "tool-${name}", toolCallId,
|
|
744
|
+
* state: "input-available", input }`
|
|
745
|
+
* - `tool-approval-request` parts → skipped (AI SDK derives the
|
|
746
|
+
* approval state from chunks during processing)
|
|
747
|
+
*
|
|
748
|
+
* @internal
|
|
749
|
+
*/
|
|
750
|
+
function synthesizeHandoverUIMessage(partial, messageId) {
|
|
751
|
+
const assistant = partial.find((m) => m.role === "assistant");
|
|
752
|
+
if (!assistant || typeof assistant.content === "string")
|
|
753
|
+
return undefined;
|
|
754
|
+
const parts = [];
|
|
755
|
+
for (const part of assistant.content) {
|
|
756
|
+
if (part.type === "text" && typeof part.text === "string") {
|
|
757
|
+
parts.push({ type: "text", text: part.text });
|
|
758
|
+
}
|
|
759
|
+
else if (part.type === "tool-call" && part.toolCallId && part.toolName) {
|
|
760
|
+
parts.push({
|
|
761
|
+
type: `tool-${part.toolName}`,
|
|
762
|
+
toolCallId: part.toolCallId,
|
|
763
|
+
state: "input-available",
|
|
764
|
+
input: part.input,
|
|
765
|
+
});
|
|
766
|
+
}
|
|
767
|
+
// tool-approval-request parts intentionally skipped — they're an
|
|
768
|
+
// AI-SDK protocol detail, not a UI surface.
|
|
769
|
+
}
|
|
770
|
+
if (parts.length === 0)
|
|
771
|
+
return undefined;
|
|
772
|
+
// Use the customer's step-1 messageId if provided (so the agent's
|
|
773
|
+
// post-handover chunks merge into the same assistant message on the
|
|
774
|
+
// browser). Fall back to a fresh id only if the handover signal
|
|
775
|
+
// didn't carry one.
|
|
776
|
+
return {
|
|
777
|
+
id: messageId ?? (0, ai_1.generateId)(),
|
|
778
|
+
role: "assistant",
|
|
779
|
+
parts,
|
|
780
|
+
};
|
|
781
|
+
}
|
|
681
782
|
/**
|
|
682
783
|
* Per-turn background context queue. Messages added via `chat.backgroundWork.inject()`
|
|
683
784
|
* are drained at the next `prepareStep` boundary and appended to the model messages.
|
|
@@ -1798,6 +1899,59 @@ function chatAgent(options) {
|
|
|
1798
1899
|
return;
|
|
1799
1900
|
}
|
|
1800
1901
|
}
|
|
1902
|
+
// Handle handover-prepare runs — wait on session.in for the
|
|
1903
|
+
// customer's `chat.handover` route handler to either hand off
|
|
1904
|
+
// mid-turn (tool calls) or signal pure-text completion.
|
|
1905
|
+
if (payload.trigger === "handover-prepare") {
|
|
1906
|
+
if (activeSpan) {
|
|
1907
|
+
activeSpan.setAttribute("chat.handoverPreparing", true);
|
|
1908
|
+
}
|
|
1909
|
+
const handoverResult = await handoverInput.waitWithIdleTimeout({
|
|
1910
|
+
idleTimeoutInSeconds: idleTimeoutInSeconds ?? payload.idleTimeoutInSeconds ?? 60,
|
|
1911
|
+
spanName: "waiting for handover signal",
|
|
1912
|
+
});
|
|
1913
|
+
if (!handoverResult.ok) {
|
|
1914
|
+
// Handler crashed before signaling — exit cleanly.
|
|
1915
|
+
return;
|
|
1916
|
+
}
|
|
1917
|
+
if (handoverResult.output.kind === "handover-skip") {
|
|
1918
|
+
// Sent only when the customer's handler aborts before
|
|
1919
|
+
// producing a finishReason. Normal pure-text and
|
|
1920
|
+
// tool-call finishes go through `kind: "handover"` with
|
|
1921
|
+
// `isFinal: true | false`. Exit without firing any turn
|
|
1922
|
+
// hooks.
|
|
1923
|
+
return;
|
|
1924
|
+
}
|
|
1925
|
+
// kind === "handover": stash the partial assistant message
|
|
1926
|
+
// so turn-0 setup can append it after loading user
|
|
1927
|
+
// messages. Two branches downstream, switched by `isFinal`:
|
|
1928
|
+
// - `false`: customer's step 1 ended with `tool-calls`.
|
|
1929
|
+
// The agent's `streamText` sees pending tool-calls (via
|
|
1930
|
+
// the approval round in the partial) and executes them,
|
|
1931
|
+
// then runs step 2's LLM call.
|
|
1932
|
+
// - `true`: customer's step 1 ended pure-text. The agent
|
|
1933
|
+
// runs the turn-loop hooks but SKIPS the `streamText`
|
|
1934
|
+
// call entirely (the response is already complete).
|
|
1935
|
+
// `onTurnComplete` fires with the partial as
|
|
1936
|
+
// `responseMessage` so persistence works normally.
|
|
1937
|
+
locals_js_1.locals.set(chatHandoverPartialKey, handoverResult.output.partialAssistantMessage);
|
|
1938
|
+
// Stash the customer-side step-1 messageId. Turn-0 setup
|
|
1939
|
+
// uses it to seed the synthesized partial UIMessage with the
|
|
1940
|
+
// SAME id, so the agent's post-handover chunks merge into
|
|
1941
|
+
// the same assistant message on the browser side.
|
|
1942
|
+
if (handoverResult.output.messageId) {
|
|
1943
|
+
locals_js_1.locals.set(chatHandoverMessageIdKey, handoverResult.output.messageId);
|
|
1944
|
+
}
|
|
1945
|
+
locals_js_1.locals.set(chatHandoverIsFinalKey, handoverResult.output.isFinal);
|
|
1946
|
+
// Synthesize a wire payload that the turn loop treats as a
|
|
1947
|
+
// normal first-turn message. The original user-history
|
|
1948
|
+
// messages came in via `payload.messages` at trigger time;
|
|
1949
|
+
// reuse them.
|
|
1950
|
+
currentWirePayload = {
|
|
1951
|
+
...payload,
|
|
1952
|
+
trigger: "submit-message",
|
|
1953
|
+
};
|
|
1954
|
+
}
|
|
1801
1955
|
for (let turn = 0; turn < maxTurns; turn++) {
|
|
1802
1956
|
try {
|
|
1803
1957
|
// Extract turn-level context before entering the span
|
|
@@ -2122,6 +2276,38 @@ function chatAgent(options) {
|
|
|
2122
2276
|
if (lastModel)
|
|
2123
2277
|
turnNewModelMessages.push(lastModel);
|
|
2124
2278
|
}
|
|
2279
|
+
// If a `chat.handover` route handler signalled a
|
|
2280
|
+
// mid-turn handover, splice its partial assistant
|
|
2281
|
+
// response (text + pending tool-calls + the
|
|
2282
|
+
// synthesized tool-approval round) onto the
|
|
2283
|
+
// accumulator. `streamText` will hit AI SDK's
|
|
2284
|
+
// initial-tool-execution branch, run the
|
|
2285
|
+
// agent-side tool executes, and resume from step 2
|
|
2286
|
+
// — skipping the first model call (already done
|
|
2287
|
+
// by the handler).
|
|
2288
|
+
//
|
|
2289
|
+
// We also synthesize a UIMessage form of the
|
|
2290
|
+
// partial assistant and push it to
|
|
2291
|
+
// `accumulatedUIMessages`. AI SDK's
|
|
2292
|
+
// `processUIMessageStream` (invoked when our
|
|
2293
|
+
// run-loop calls `runResult.toUIMessageStream({
|
|
2294
|
+
// onFinish })`) initializes `state.message` from
|
|
2295
|
+
// the trailing assistant in `originalMessages`.
|
|
2296
|
+
// Without that, the `tool-output-available`
|
|
2297
|
+
// chunks emitted by the initial-tool-execution
|
|
2298
|
+
// branch can't find their matching tool-call in
|
|
2299
|
+
// state and AI SDK throws
|
|
2300
|
+
// `UIMessageStreamError: No tool invocation found`.
|
|
2301
|
+
const pendingHandoverPartial = locals_js_1.locals.get(chatHandoverPartialKey);
|
|
2302
|
+
if (pendingHandoverPartial && pendingHandoverPartial.length > 0) {
|
|
2303
|
+
accumulatedMessages.push(...pendingHandoverPartial);
|
|
2304
|
+
const handoverMessageId = locals_js_1.locals.get(chatHandoverMessageIdKey);
|
|
2305
|
+
const partialUI = synthesizeHandoverUIMessage(pendingHandoverPartial, handoverMessageId);
|
|
2306
|
+
if (partialUI) {
|
|
2307
|
+
accumulatedUIMessages.push(partialUI);
|
|
2308
|
+
}
|
|
2309
|
+
locals_js_1.locals.set(chatHandoverPartialKey, []); // consume once
|
|
2310
|
+
}
|
|
2125
2311
|
}
|
|
2126
2312
|
else if (currentWirePayload.trigger === "regenerate-message") {
|
|
2127
2313
|
// Regenerate: frontend sent full history with last assistant message
|
|
@@ -2281,6 +2467,18 @@ function chatAgent(options) {
|
|
|
2281
2467
|
});
|
|
2282
2468
|
let onFinishAttached = false;
|
|
2283
2469
|
let runResult;
|
|
2470
|
+
// Pure-text head-start: customer's step 1 IS the
|
|
2471
|
+
// final response. Skip the user's `run` callback
|
|
2472
|
+
// (no LLM call) and use the synthesized partial
|
|
2473
|
+
// UIMessage as `capturedResponseMessage`. The post-
|
|
2474
|
+
// turn flow (`onBeforeTurnComplete` →
|
|
2475
|
+
// `onTurnComplete` → trigger:turn-complete) fires
|
|
2476
|
+
// normally so persistence works.
|
|
2477
|
+
const headStartIsFinal = locals_js_1.locals.get(chatHandoverIsFinalKey);
|
|
2478
|
+
const isHeadStartFinalTurn = turn === 0 && headStartIsFinal === true;
|
|
2479
|
+
if (isHeadStartFinalTurn) {
|
|
2480
|
+
locals_js_1.locals.set(chatHandoverIsFinalKey, undefined); // consume once
|
|
2481
|
+
}
|
|
2284
2482
|
try {
|
|
2285
2483
|
// Drain any messages injected by background work (e.g. self-review from previous turn).
|
|
2286
2484
|
// Skip if the last message is a tool message — appending after it would
|
|
@@ -2292,20 +2490,35 @@ function chatAgent(options) {
|
|
|
2292
2490
|
if (bgQueue && bgQueue.length > 0 && lastAccumulated?.role !== "tool") {
|
|
2293
2491
|
accumulatedMessages.push(...bgQueue.splice(0));
|
|
2294
2492
|
}
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2493
|
+
if (isHeadStartFinalTurn) {
|
|
2494
|
+
// The synthesized partial UIMessage IS the response.
|
|
2495
|
+
// It was pushed to `accumulatedUIMessages` during the
|
|
2496
|
+
// submit-message branch's splice; recover it as the
|
|
2497
|
+
// last assistant.
|
|
2498
|
+
const lastUI = accumulatedUIMessages[accumulatedUIMessages.length - 1];
|
|
2499
|
+
if (lastUI && lastUI.role === "assistant") {
|
|
2500
|
+
capturedResponseMessage = lastUI;
|
|
2501
|
+
capturedFinishReason = "stop";
|
|
2502
|
+
}
|
|
2503
|
+
// Don't call userRun. Don't pipe. Skip directly
|
|
2504
|
+
// to the post-turn flow below.
|
|
2505
|
+
}
|
|
2506
|
+
else {
|
|
2507
|
+
runResult = await userRun({
|
|
2508
|
+
...restWire,
|
|
2509
|
+
messages: await applyPrepareMessages(accumulatedMessages, "run"),
|
|
2510
|
+
clientData,
|
|
2511
|
+
continuation,
|
|
2512
|
+
previousRunId,
|
|
2513
|
+
preloaded,
|
|
2514
|
+
previousTurnUsage,
|
|
2515
|
+
totalUsage: cumulativeUsage,
|
|
2516
|
+
ctx,
|
|
2517
|
+
signal: combinedSignal,
|
|
2518
|
+
cancelSignal,
|
|
2519
|
+
stopSignal,
|
|
2520
|
+
});
|
|
2521
|
+
}
|
|
2309
2522
|
// Auto-pipe if the run function returned a StreamTextResult or similar,
|
|
2310
2523
|
// but only if pipeChat() wasn't already called manually during this turn.
|
|
2311
2524
|
// We call toUIMessageStream ourselves to attach onFinish for response capture.
|