@convex-dev/agent 0.6.4 → 0.7.0-alpha.0
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/MIGRATION.md +60 -116
- package/dist/client/messages.d.ts +1 -398
- package/dist/client/messages.d.ts.map +1 -1
- package/dist/client/messages.js +3 -47
- package/dist/client/messages.js.map +1 -1
- package/dist/client/types.d.ts +1 -455
- package/dist/client/types.d.ts.map +1 -1
- package/dist/component/_generated/component.d.ts +805 -45
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/dist/component/messages.d.ts +1252 -201
- package/dist/component/messages.d.ts.map +1 -1
- package/dist/component/messages.js +49 -23
- package/dist/component/messages.js.map +1 -1
- package/dist/component/schema.d.ts +2593 -332
- package/dist/component/schema.d.ts.map +1 -1
- package/dist/component/streams.d.ts +7 -1
- package/dist/component/streams.d.ts.map +1 -1
- package/dist/component/streams.js +24 -30
- package/dist/component/streams.js.map +1 -1
- package/dist/component/threads.d.ts.map +1 -1
- package/dist/component/threads.js +1 -2
- package/dist/component/threads.js.map +1 -1
- package/dist/react/index.d.ts +5 -5
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +5 -5
- package/dist/react/index.js.map +1 -1
- package/dist/shared.d.ts +19 -7
- package/dist/shared.d.ts.map +1 -1
- package/dist/shared.js +4 -4
- package/dist/shared.js.map +1 -1
- package/dist/streaming/materializePersistedUIMessageChunks.d.ts +16 -0
- package/dist/streaming/materializePersistedUIMessageChunks.d.ts.map +1 -0
- package/dist/streaming/materializePersistedUIMessageChunks.js +302 -0
- package/dist/streaming/materializePersistedUIMessageChunks.js.map +1 -0
- package/dist/streaming/persistedUIMessageChunks.d.ts +106 -0
- package/dist/streaming/persistedUIMessageChunks.d.ts.map +1 -0
- package/dist/streaming/persistedUIMessageChunks.js +475 -0
- package/dist/streaming/persistedUIMessageChunks.js.map +1 -0
- package/dist/validators.d.ts +10026 -1055
- package/dist/validators.d.ts.map +1 -1
- package/dist/validators.js +71 -9
- package/dist/validators.js.map +1 -1
- package/dist/{UIMessages.d.ts → vercel/UIMessages.d.ts} +1 -1
- package/dist/vercel/UIMessages.d.ts.map +1 -0
- package/dist/{UIMessages.js → vercel/UIMessages.js} +63 -1
- package/dist/vercel/UIMessages.js.map +1 -0
- package/dist/{client → vercel/client}/createTool.d.ts +23 -16
- package/dist/vercel/client/createTool.d.ts.map +1 -0
- package/dist/{client → vercel/client}/createTool.js +2 -0
- package/dist/vercel/client/createTool.js.map +1 -0
- package/dist/{client → vercel/client}/definePlaygroundAPI.d.ts +533 -79
- package/dist/{client → vercel/client}/definePlaygroundAPI.d.ts.map +1 -1
- package/dist/{client → vercel/client}/definePlaygroundAPI.js +8 -7
- package/dist/vercel/client/definePlaygroundAPI.js.map +1 -0
- package/dist/{client → vercel/client}/files.d.ts +2 -2
- package/dist/vercel/client/files.d.ts.map +1 -0
- package/dist/vercel/client/files.js.map +1 -0
- package/dist/vercel/client/messages.d.ts +542 -0
- package/dist/vercel/client/messages.d.ts.map +1 -0
- package/dist/vercel/client/messages.js +66 -0
- package/dist/vercel/client/messages.js.map +1 -0
- package/dist/{client → vercel/client}/mockModel.d.ts +17 -17
- package/dist/vercel/client/mockModel.d.ts.map +1 -0
- package/dist/{client → vercel/client}/mockModel.js +12 -13
- package/dist/vercel/client/mockModel.js.map +1 -0
- package/dist/{client → vercel/client}/saveInputMessages.d.ts +2 -2
- package/dist/vercel/client/saveInputMessages.d.ts.map +1 -0
- package/dist/vercel/client/saveInputMessages.js.map +1 -0
- package/dist/{client → vercel/client}/search.d.ts +119 -22
- package/dist/vercel/client/search.d.ts.map +1 -0
- package/dist/{client → vercel/client}/search.js +2 -2
- package/dist/vercel/client/search.js.map +1 -0
- package/dist/{client → vercel/client}/start.d.ts +26 -11
- package/dist/vercel/client/start.d.ts.map +1 -0
- package/dist/{client → vercel/client}/start.js +64 -22
- package/dist/vercel/client/start.js.map +1 -0
- package/dist/{client → vercel/client}/streamText.d.ts +13 -12
- package/dist/vercel/client/streamText.d.ts.map +1 -0
- package/dist/{client → vercel/client}/streamText.js +43 -9
- package/dist/vercel/client/streamText.js.map +1 -0
- package/dist/{client → vercel/client}/streaming.d.ts +1410 -183
- package/dist/vercel/client/streaming.d.ts.map +1 -0
- package/dist/{client → vercel/client}/streaming.js +120 -52
- package/dist/vercel/client/streaming.js.map +1 -0
- package/dist/vercel/client/types.d.ts +456 -0
- package/dist/vercel/client/types.d.ts.map +1 -0
- package/dist/vercel/client/types.js +3 -0
- package/dist/vercel/client/types.js.map +1 -0
- package/dist/{client → vercel/client}/utils.d.ts +3 -2
- package/dist/vercel/client/utils.d.ts.map +1 -0
- package/dist/{client → vercel/client}/utils.js +2 -2
- package/dist/vercel/client/utils.js.map +1 -0
- package/dist/{deltas.d.ts → vercel/deltas.d.ts} +3 -4
- package/dist/vercel/deltas.d.ts.map +1 -0
- package/dist/{deltas.js → vercel/deltas.js} +195 -99
- package/dist/vercel/deltas.js.map +1 -0
- package/dist/{client → vercel}/index.d.ts +482 -106
- package/dist/vercel/index.d.ts.map +1 -0
- package/dist/{client → vercel}/index.js +64 -46
- package/dist/vercel/index.js.map +1 -0
- package/dist/{mapping.d.ts → vercel/mapping.d.ts} +5 -19
- package/dist/vercel/mapping.d.ts.map +1 -0
- package/dist/{mapping.js → vercel/mapping.js} +411 -114
- package/dist/vercel/mapping.js.map +1 -0
- package/dist/{react → vercel/react}/optimisticallySendMessage.d.ts +2 -2
- package/dist/vercel/react/optimisticallySendMessage.d.ts.map +1 -0
- package/dist/vercel/react/optimisticallySendMessage.js.map +1 -0
- package/dist/{react → vercel/react}/types.d.ts +1 -1
- package/dist/vercel/react/types.d.ts.map +1 -0
- package/dist/{react → vercel/react}/types.js.map +1 -1
- package/dist/{react → vercel/react}/useDeltaStreams.d.ts +1 -1
- package/dist/vercel/react/useDeltaStreams.d.ts.map +1 -0
- package/dist/{react → vercel/react}/useDeltaStreams.js +1 -1
- package/dist/vercel/react/useDeltaStreams.js.map +1 -0
- package/dist/{react → vercel/react}/useStreamingUIMessages.d.ts +1 -1
- package/dist/vercel/react/useStreamingUIMessages.d.ts.map +1 -0
- package/dist/{react → vercel/react}/useStreamingUIMessages.js +7 -3
- package/dist/vercel/react/useStreamingUIMessages.js.map +1 -0
- package/dist/{react → vercel/react}/useThreadMessages.d.ts +1 -1
- package/dist/vercel/react/useThreadMessages.d.ts.map +1 -0
- package/dist/{react → vercel/react}/useThreadMessages.js +1 -1
- package/dist/vercel/react/useThreadMessages.js.map +1 -0
- package/dist/{react → vercel/react}/useUIMessages.d.ts +1 -1
- package/dist/vercel/react/useUIMessages.d.ts.map +1 -0
- package/dist/{react → vercel/react}/useUIMessages.js +1 -1
- package/dist/vercel/react/useUIMessages.js.map +1 -0
- package/package.json +29 -22
- package/src/client/messages.ts +5 -107
- package/src/client/types.ts +0 -608
- package/src/component/_generated/component.ts +1089 -48
- package/src/component/messages.test.ts +273 -1
- package/src/component/messages.ts +62 -32
- package/src/component/streams.ts +47 -47
- package/src/component/threads.ts +5 -2
- package/src/eslintConfig.test.ts +44 -0
- package/src/react/index.ts +8 -5
- package/src/shared.ts +26 -36
- package/src/streaming/materializePersistedUIMessageChunks.test.ts +513 -0
- package/src/streaming/materializePersistedUIMessageChunks.ts +398 -0
- package/src/streaming/persistedUIMessageChunks.test.ts +205 -0
- package/src/streaming/persistedUIMessageChunks.ts +658 -0
- package/src/validators.test.ts +4 -1
- package/src/validators.ts +89 -8
- package/src/{UIMessages.combineUIMessages.test.ts → vercel/UIMessages.combineUIMessages.test.ts} +4 -1
- package/src/{UIMessages.ts → vercel/UIMessages.ts} +95 -3
- package/src/{client → vercel/client}/approval.test.ts +23 -13
- package/src/{client → vercel/client}/createTool.ts +62 -22
- package/src/{client → vercel/client}/definePlaygroundAPI.ts +15 -16
- package/src/{client → vercel/client}/files.ts +2 -2
- package/src/{client → vercel/client}/index.test.ts +99 -31
- package/src/vercel/client/messages.ts +182 -0
- package/src/{client → vercel/client}/mockModel.ts +38 -39
- package/src/{client → vercel/client}/saveInputMessages.test.ts +1 -1
- package/src/{client → vercel/client}/saveInputMessages.ts +3 -3
- package/src/{client → vercel/client}/search.test.ts +3 -3
- package/src/{client → vercel/client}/search.ts +4 -4
- package/src/{client → vercel/client}/setup.test.ts +6 -3
- package/src/vercel/client/start.test.ts +14 -0
- package/src/{client → vercel/client}/start.ts +147 -36
- package/src/vercel/client/streamText.test.ts +223 -0
- package/src/{client → vercel/client}/streamText.ts +102 -31
- package/src/{client → vercel/client}/streaming.integration.test.ts +6 -3
- package/src/{client → vercel/client}/streaming.test.ts +178 -4
- package/src/{client → vercel/client}/streaming.ts +114 -52
- package/src/vercel/client/types.test.ts +13 -0
- package/src/vercel/client/types.ts +679 -0
- package/src/{client → vercel/client}/utils.ts +13 -7
- package/src/{deltas.test.ts → vercel/deltas.test.ts} +384 -93
- package/src/{deltas.ts → vercel/deltas.ts} +218 -115
- package/src/{fromUIMessages.test.ts → vercel/fromUIMessages.test.ts} +17 -1
- package/src/{client → vercel}/index.ts +167 -84
- package/src/{mapping.test.ts → vercel/mapping.test.ts} +377 -92
- package/src/{mapping.ts → vercel/mapping.ts} +501 -137
- package/src/{react → vercel/react}/optimisticallySendMessage.ts +2 -2
- package/src/{react → vercel/react}/types.ts +1 -1
- package/src/{react → vercel/react}/useDeltaStreams.ts +6 -2
- package/src/{react → vercel/react}/useStreamingUIMessages.ts +7 -3
- package/src/{react → vercel/react}/useThreadMessages.ts +2 -2
- package/src/{react → vercel/react}/useUIMessages.ts +2 -2
- package/src/{toUIMessages.test.ts → vercel/toUIMessages.test.ts} +49 -2
- package/dist/UIMessages.d.ts.map +0 -1
- package/dist/UIMessages.js.map +0 -1
- package/dist/client/createTool.d.ts.map +0 -1
- package/dist/client/createTool.js.map +0 -1
- package/dist/client/definePlaygroundAPI.js.map +0 -1
- package/dist/client/files.d.ts.map +0 -1
- package/dist/client/files.js.map +0 -1
- package/dist/client/index.d.ts.map +0 -1
- package/dist/client/index.js.map +0 -1
- package/dist/client/mockModel.d.ts.map +0 -1
- package/dist/client/mockModel.js.map +0 -1
- package/dist/client/saveInputMessages.d.ts.map +0 -1
- package/dist/client/saveInputMessages.js.map +0 -1
- package/dist/client/search.d.ts.map +0 -1
- package/dist/client/search.js.map +0 -1
- package/dist/client/start.d.ts.map +0 -1
- package/dist/client/start.js.map +0 -1
- package/dist/client/streamText.d.ts.map +0 -1
- package/dist/client/streamText.js.map +0 -1
- package/dist/client/streaming.d.ts.map +0 -1
- package/dist/client/streaming.js.map +0 -1
- package/dist/client/utils.d.ts.map +0 -1
- package/dist/client/utils.js.map +0 -1
- package/dist/deltas.d.ts.map +0 -1
- package/dist/deltas.js.map +0 -1
- package/dist/mapping.d.ts.map +0 -1
- package/dist/mapping.js.map +0 -1
- package/dist/react/optimisticallySendMessage.d.ts.map +0 -1
- package/dist/react/optimisticallySendMessage.js.map +0 -1
- package/dist/react/types.d.ts.map +0 -1
- package/dist/react/useDeltaStreams.d.ts.map +0 -1
- package/dist/react/useDeltaStreams.js.map +0 -1
- package/dist/react/useStreamingUIMessages.d.ts.map +0 -1
- package/dist/react/useStreamingUIMessages.js.map +0 -1
- package/dist/react/useThreadMessages.d.ts.map +0 -1
- package/dist/react/useThreadMessages.js.map +0 -1
- package/dist/react/useUIMessages.d.ts.map +0 -1
- package/dist/react/useUIMessages.js.map +0 -1
- package/src/client/streamText.test.ts +0 -102
- /package/dist/{client → vercel/client}/files.js +0 -0
- /package/dist/{client → vercel/client}/saveInputMessages.js +0 -0
- /package/dist/{react → vercel/react}/optimisticallySendMessage.js +0 -0
- /package/dist/{react → vercel/react}/types.js +0 -0
- /package/src/{UIMessages.test.ts → vercel/UIMessages.test.ts} +0 -0
- /package/src/{react → vercel/react}/useUIMessages.test.ts +0 -0
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
ModelMessage,
|
|
2
3
|
StepResult,
|
|
3
4
|
StreamTextResult,
|
|
4
5
|
ToolSet,
|
|
5
6
|
UIMessage as AIUIMessage,
|
|
6
7
|
} from "ai";
|
|
8
|
+
import type { Context } from "@ai-sdk/provider-utils";
|
|
7
9
|
import { streamText as streamTextAi } from "ai";
|
|
8
10
|
import {
|
|
9
11
|
compressUIMessageChunks,
|
|
@@ -14,16 +16,31 @@ import {
|
|
|
14
16
|
import type {
|
|
15
17
|
ActionCtx,
|
|
16
18
|
AgentComponent,
|
|
17
|
-
AgentPrompt,
|
|
18
19
|
GenerationOutputMetadata,
|
|
19
20
|
Options,
|
|
20
|
-
|
|
21
|
+
StreamingTextArgs,
|
|
21
22
|
} from "./types.js";
|
|
23
|
+
import type { Output as AISDKOutput } from "ai";
|
|
22
24
|
import { startGeneration } from "./start.js";
|
|
23
|
-
import type { Agent } from "
|
|
24
|
-
import { getModelName, getProviderName } from "
|
|
25
|
+
import type { Agent } from "../index.js";
|
|
26
|
+
import { getModelName, getProviderName } from "../../shared.js";
|
|
25
27
|
import { errorToString, willContinue } from "./utils.js";
|
|
26
28
|
|
|
29
|
+
/** Finish every abort cleanup path before surfacing an internal failure. */
|
|
30
|
+
export async function runAbortCleanup(cleanup: {
|
|
31
|
+
failCall: () => Promise<void>;
|
|
32
|
+
failStreamer: () => Promise<void>;
|
|
33
|
+
onAbort?: () => PromiseLike<void> | void;
|
|
34
|
+
}): Promise<void> {
|
|
35
|
+
const results = await Promise.allSettled([
|
|
36
|
+
cleanup.failCall(),
|
|
37
|
+
cleanup.failStreamer(),
|
|
38
|
+
]);
|
|
39
|
+
await cleanup.onAbort?.();
|
|
40
|
+
const failure = results.find((result) => result.status === "rejected");
|
|
41
|
+
if (failure) throw failure.reason;
|
|
42
|
+
}
|
|
43
|
+
|
|
27
44
|
/**
|
|
28
45
|
* This behaves like {@link streamText} from the "ai" package except that
|
|
29
46
|
* it add context based on the userId and threadId and saves the input and
|
|
@@ -32,8 +49,14 @@ import { errorToString, willContinue } from "./utils.js";
|
|
|
32
49
|
* to a thread (and optionally userId).
|
|
33
50
|
*/
|
|
34
51
|
export async function streamText<
|
|
35
|
-
|
|
36
|
-
|
|
52
|
+
AgentTools extends ToolSet,
|
|
53
|
+
TOOLS extends ToolSet | undefined = undefined,
|
|
54
|
+
OUTPUT extends AISDKOutput.Output<any, any, any> = AISDKOutput.Output<
|
|
55
|
+
string,
|
|
56
|
+
string,
|
|
57
|
+
never
|
|
58
|
+
>,
|
|
59
|
+
RUNTIME_CONTEXT extends Context = Context,
|
|
37
60
|
>(
|
|
38
61
|
ctx: ActionCtx,
|
|
39
62
|
component: AgentComponent,
|
|
@@ -41,17 +64,7 @@ export async function streamText<
|
|
|
41
64
|
* The arguments to the streamText function, similar to the ai sdk's
|
|
42
65
|
* {@link streamText} function, along with Agent prompt options.
|
|
43
66
|
*/
|
|
44
|
-
streamTextArgs:
|
|
45
|
-
Omit<
|
|
46
|
-
Parameters<typeof streamTextAi<TOOLS, OUTPUT>>[0],
|
|
47
|
-
"model" | "prompt" | "messages"
|
|
48
|
-
> & {
|
|
49
|
-
/**
|
|
50
|
-
* The tools to use for the tool calls. This will override tools specified
|
|
51
|
-
* in the Agent constructor or createThread / continueThread.
|
|
52
|
-
*/
|
|
53
|
-
tools?: TOOLS;
|
|
54
|
-
},
|
|
67
|
+
streamTextArgs: StreamingTextArgs<AgentTools, TOOLS, OUTPUT, RUNTIME_CONTEXT>,
|
|
55
68
|
/**
|
|
56
69
|
* The {@link ContextOptions} and {@link StorageOptions}
|
|
57
70
|
* options to use for fetching contextual messages and saving input/output messages.
|
|
@@ -73,17 +86,49 @@ export async function streamText<
|
|
|
73
86
|
saveStreamDeltas?: boolean | StreamingOptions;
|
|
74
87
|
agentForToolCtx?: Agent;
|
|
75
88
|
},
|
|
76
|
-
): Promise<
|
|
89
|
+
): Promise<
|
|
90
|
+
StreamTextResult<
|
|
91
|
+
TOOLS extends undefined ? AgentTools : TOOLS,
|
|
92
|
+
RUNTIME_CONTEXT,
|
|
93
|
+
OUTPUT
|
|
94
|
+
> &
|
|
95
|
+
GenerationOutputMetadata
|
|
96
|
+
> {
|
|
97
|
+
type Tools = TOOLS extends undefined ? AgentTools : TOOLS;
|
|
77
98
|
const { threadId } = options ?? {};
|
|
78
99
|
const { args, userId, order, stepOrder, promptMessageId, ...call } =
|
|
79
|
-
await startGeneration
|
|
100
|
+
await startGeneration<
|
|
101
|
+
StreamingTextArgs<AgentTools, TOOLS, OUTPUT, RUNTIME_CONTEXT>,
|
|
102
|
+
Tools,
|
|
103
|
+
object,
|
|
104
|
+
RUNTIME_CONTEXT
|
|
105
|
+
>(
|
|
106
|
+
ctx,
|
|
107
|
+
component,
|
|
108
|
+
streamTextArgs,
|
|
109
|
+
options,
|
|
110
|
+
"streamText",
|
|
111
|
+
);
|
|
80
112
|
|
|
81
|
-
const steps: StepResult<
|
|
113
|
+
const steps: StepResult<Tools, RUNTIME_CONTEXT>[] = [];
|
|
114
|
+
let initialResponseMessages: ModelMessage[] = [];
|
|
115
|
+
let initialResponseMessagesSaved = false;
|
|
116
|
+
const responseMessagesForStep = (
|
|
117
|
+
step: StepResult<Tools, RUNTIME_CONTEXT>,
|
|
118
|
+
) => [
|
|
119
|
+
...(initialResponseMessagesSaved ? [] : initialResponseMessages),
|
|
120
|
+
...step.response.messages,
|
|
121
|
+
];
|
|
82
122
|
|
|
83
123
|
// Track the final step for atomic save with stream finish (issue #181).
|
|
84
124
|
// Only used when streamText awaits stream consumption itself; the
|
|
85
125
|
// `returnImmediately` path saves inline instead (see onStepFinish below).
|
|
86
|
-
let pendingFinalStep:
|
|
126
|
+
let pendingFinalStep:
|
|
127
|
+
| {
|
|
128
|
+
step: StepResult<Tools, RUNTIME_CONTEXT>;
|
|
129
|
+
responseMessages: ModelMessage[];
|
|
130
|
+
}
|
|
131
|
+
| undefined;
|
|
87
132
|
|
|
88
133
|
// Whether streamText will await stream consumption before returning.
|
|
89
134
|
// When false (saveStreamDeltas.returnImmediately === true), we cannot
|
|
@@ -123,7 +168,7 @@ export async function streamText<
|
|
|
123
168
|
)
|
|
124
169
|
: undefined;
|
|
125
170
|
|
|
126
|
-
const result = streamTextAi({
|
|
171
|
+
const result = streamTextAi<Tools, RUNTIME_CONTEXT, OUTPUT>({
|
|
127
172
|
...args,
|
|
128
173
|
abortSignal: streamer?.abortController.signal ?? args.abortSignal,
|
|
129
174
|
experimental_transform: mergeTransforms(
|
|
@@ -136,7 +181,19 @@ export async function streamText<
|
|
|
136
181
|
await streamer?.fail(errorToString(error.error));
|
|
137
182
|
return streamTextArgs.onError?.(error);
|
|
138
183
|
},
|
|
184
|
+
onAbort: async (event) => {
|
|
185
|
+
const reason = args.abortSignal?.reason
|
|
186
|
+
? errorToString(args.abortSignal.reason)
|
|
187
|
+
: "streamText aborted";
|
|
188
|
+
await runAbortCleanup({
|
|
189
|
+
failCall: () => call.fail(reason),
|
|
190
|
+
failStreamer: async () => streamer?.fail(reason),
|
|
191
|
+
onAbort: () => streamTextArgs.onAbort?.(event),
|
|
192
|
+
});
|
|
193
|
+
},
|
|
139
194
|
prepareStep: async (options) => {
|
|
195
|
+
if (options.stepNumber === 0)
|
|
196
|
+
initialResponseMessages = [...options.responseMessages];
|
|
140
197
|
const result = await streamTextArgs.prepareStep?.(options);
|
|
141
198
|
if (result) {
|
|
142
199
|
const model = result.model ?? options.model;
|
|
@@ -150,7 +207,7 @@ export async function streamText<
|
|
|
150
207
|
}
|
|
151
208
|
return undefined;
|
|
152
209
|
},
|
|
153
|
-
|
|
210
|
+
onStepEnd: async (step) => {
|
|
154
211
|
steps.push(step);
|
|
155
212
|
const createPendingMessage = await willContinue(steps, args.stopWhen);
|
|
156
213
|
if (!createPendingMessage && streamer) {
|
|
@@ -159,22 +216,36 @@ export async function streamText<
|
|
|
159
216
|
if (willAwaitStream) {
|
|
160
217
|
// We're about to `await stream` below — defer the save so it
|
|
161
218
|
// happens atomically with stream finish (issue #181).
|
|
162
|
-
pendingFinalStep =
|
|
219
|
+
pendingFinalStep = {
|
|
220
|
+
step,
|
|
221
|
+
responseMessages: responseMessagesForStep(step),
|
|
222
|
+
};
|
|
163
223
|
} else {
|
|
164
224
|
// returnImmediately path: streamText is about to return without
|
|
165
225
|
// awaiting consumption, so the deferred-save block below won't
|
|
166
226
|
// see this step. Save inline now (issue #265).
|
|
167
227
|
const finishStreamId = await streamer.getOrCreateStreamId();
|
|
168
|
-
await call.save(
|
|
228
|
+
await call.save(
|
|
229
|
+
{ step, responseMessages: responseMessagesForStep(step) },
|
|
230
|
+
false,
|
|
231
|
+
finishStreamId,
|
|
232
|
+
);
|
|
233
|
+
initialResponseMessagesSaved = true;
|
|
169
234
|
}
|
|
170
235
|
} else {
|
|
171
|
-
await call.save(
|
|
236
|
+
await call.save(
|
|
237
|
+
{ step, responseMessages: responseMessagesForStep(step) },
|
|
238
|
+
createPendingMessage,
|
|
239
|
+
);
|
|
240
|
+
initialResponseMessagesSaved = true;
|
|
172
241
|
}
|
|
173
|
-
return
|
|
242
|
+
return (streamTextArgs.onStepEnd ?? streamTextArgs.onStepFinish)?.(step);
|
|
174
243
|
},
|
|
175
|
-
}
|
|
244
|
+
} as Parameters<
|
|
245
|
+
typeof streamTextAi<Tools, RUNTIME_CONTEXT, OUTPUT>
|
|
246
|
+
>[0]) as StreamTextResult<Tools, RUNTIME_CONTEXT, OUTPUT>;
|
|
176
247
|
const stream = streamer?.consumeStream(
|
|
177
|
-
result.toUIMessageStream<AIUIMessage<
|
|
248
|
+
result.toUIMessageStream<AIUIMessage<Tools>>(),
|
|
178
249
|
);
|
|
179
250
|
if (willAwaitStream) {
|
|
180
251
|
try {
|
|
@@ -188,7 +259,7 @@ export async function streamText<
|
|
|
188
259
|
// Save the deferred final step if it was already generated but not yet persisted
|
|
189
260
|
if (pendingFinalStep) {
|
|
190
261
|
try {
|
|
191
|
-
await call.save(
|
|
262
|
+
await call.save(pendingFinalStep, false);
|
|
192
263
|
} catch (saveError) {
|
|
193
264
|
console.error("Failed to save deferred final step:", saveError);
|
|
194
265
|
}
|
|
@@ -201,7 +272,7 @@ export async function streamText<
|
|
|
201
272
|
// If we deferred the final step save, do it now with atomic stream finish.
|
|
202
273
|
if (pendingFinalStep && streamer) {
|
|
203
274
|
const finishStreamId = await streamer.getOrCreateStreamId();
|
|
204
|
-
await call.save(
|
|
275
|
+
await call.save(pendingFinalStep, false, finishStreamId);
|
|
205
276
|
}
|
|
206
277
|
const metadata: GenerationOutputMetadata = {
|
|
207
278
|
promptMessageId,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, test } from "vitest";
|
|
2
|
-
import { createThread } from "
|
|
2
|
+
import { createThread } from "../../client/threads.js";
|
|
3
3
|
import type { GenericSchema, SchemaDefinition } from "convex/server";
|
|
4
4
|
import { streamText } from "ai";
|
|
5
5
|
import { components, initConvexTest } from "./setup.test.js";
|
|
@@ -9,9 +9,12 @@ import {
|
|
|
9
9
|
DeltaStreamer,
|
|
10
10
|
mergeTransforms,
|
|
11
11
|
} from "./streaming.js";
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
getParts,
|
|
14
|
+
deriveUIMessagesFromDeltas,
|
|
15
|
+
} from "../deltas.js";
|
|
13
16
|
import type { TestConvex } from "convex-test";
|
|
14
|
-
import type { StreamDelta, StreamMessage } from "
|
|
17
|
+
import type { StreamDelta, StreamMessage } from "../../validators.js";
|
|
15
18
|
import { dedupeMessages } from "../react/useUIMessages.js";
|
|
16
19
|
|
|
17
20
|
const defaultTestOptions = {
|
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, test } from "vitest";
|
|
2
|
-
import { createThread } from "
|
|
1
|
+
import { beforeEach, describe, expect, test, vi } from "vitest";
|
|
2
|
+
import { createThread } from "../../client/threads.js";
|
|
3
|
+
import type { MutationCtx } from "../../client/types.js";
|
|
3
4
|
import type { GenericSchema, SchemaDefinition } from "convex/server";
|
|
4
5
|
import { streamText } from "ai";
|
|
5
6
|
import { components, initConvexTest } from "./setup.test.js";
|
|
6
7
|
import { mockModel } from "./mockModel.js";
|
|
7
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
compressUIMessageChunks,
|
|
10
|
+
DeltaStreamer,
|
|
11
|
+
} from "./streaming.js";
|
|
8
12
|
import { getParts } from "../deltas.js";
|
|
9
13
|
import type { TestConvex } from "convex-test";
|
|
10
14
|
|
|
@@ -181,6 +185,176 @@ describe("DeltaStreamer", () => {
|
|
|
181
185
|
}
|
|
182
186
|
});
|
|
183
187
|
});
|
|
184
|
-
|
|
188
|
+
test("honors a signal that was aborted before construction", async () => {
|
|
189
|
+
const abortController = new AbortController();
|
|
190
|
+
abortController.abort();
|
|
191
|
+
|
|
192
|
+
await t.run(async (ctx) => {
|
|
193
|
+
const streamer = new DeltaStreamer<string>(
|
|
194
|
+
components.agent,
|
|
195
|
+
ctx,
|
|
196
|
+
{ ...defaultTestOptions, abortSignal: abortController.signal },
|
|
197
|
+
{ ...testMetadata, threadId },
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
expect(streamer.abortController.signal.aborted).toBe(true);
|
|
201
|
+
await streamer.addParts(["ignored"]);
|
|
202
|
+
expect(streamer.streamId).toBeUndefined();
|
|
203
|
+
await expect(streamer.getOrCreateStreamId()).rejects.toThrow(
|
|
204
|
+
"Cannot create a stream after it has been aborted",
|
|
205
|
+
);
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test("shares signal and fail cleanup while stream creation is in flight", async () => {
|
|
210
|
+
let resolveCreate!: (streamId: string) => void;
|
|
211
|
+
const creatingStream = new Promise<string>((resolve) => {
|
|
212
|
+
resolveCreate = resolve;
|
|
213
|
+
});
|
|
214
|
+
let resolveAbort!: () => void;
|
|
215
|
+
const abortingStream = new Promise<void>((resolve) => {
|
|
216
|
+
resolveAbort = resolve;
|
|
217
|
+
});
|
|
218
|
+
const runMutation = vi
|
|
219
|
+
.fn()
|
|
220
|
+
.mockImplementationOnce(() => creatingStream)
|
|
221
|
+
.mockImplementationOnce(() => abortingStream);
|
|
222
|
+
const abortController = new AbortController();
|
|
223
|
+
const streamer = new DeltaStreamer<string>(
|
|
224
|
+
components.agent,
|
|
225
|
+
{ runMutation } as unknown as MutationCtx,
|
|
226
|
+
{ ...defaultTestOptions, abortSignal: abortController.signal },
|
|
227
|
+
{ ...testMetadata, threadId },
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
const streamId = streamer.getStreamId();
|
|
231
|
+
abortController.abort();
|
|
232
|
+
const failing = streamer.fail("creation failed");
|
|
233
|
+
let failSettled = false;
|
|
234
|
+
void failing.then(() => {
|
|
235
|
+
failSettled = true;
|
|
236
|
+
});
|
|
237
|
+
resolveCreate("stream-1");
|
|
238
|
+
await expect(streamId).resolves.toBe("stream-1");
|
|
239
|
+
await vi.waitFor(() => expect(runMutation).toHaveBeenCalledTimes(2));
|
|
240
|
+
|
|
241
|
+
expect(failSettled).toBe(false);
|
|
242
|
+
resolveAbort();
|
|
243
|
+
await failing;
|
|
244
|
+
|
|
245
|
+
expect(runMutation).toHaveBeenNthCalledWith(
|
|
246
|
+
2,
|
|
247
|
+
components.agent.streams.abort,
|
|
248
|
+
{ streamId: "stream-1", reason: "abortSignal" },
|
|
249
|
+
);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
test("aborts the component stream when a delta write fails", async () => {
|
|
253
|
+
const runMutation = vi
|
|
254
|
+
.fn()
|
|
255
|
+
.mockResolvedValueOnce("stream-1")
|
|
256
|
+
.mockRejectedValueOnce(new Error("delta failed"))
|
|
257
|
+
.mockResolvedValueOnce(undefined);
|
|
258
|
+
let abortReason: string | undefined;
|
|
259
|
+
const streamer = new DeltaStreamer<string>(
|
|
260
|
+
components.agent,
|
|
261
|
+
{ runMutation } as unknown as MutationCtx,
|
|
262
|
+
{
|
|
263
|
+
...defaultTestOptions,
|
|
264
|
+
onAsyncAbort: async (reason) => {
|
|
265
|
+
abortReason = reason;
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
{ ...testMetadata, threadId },
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
await streamer.addParts(["A"]);
|
|
272
|
+
await streamer.finish();
|
|
273
|
+
|
|
274
|
+
expect(abortReason).toBe("delta failed");
|
|
275
|
+
expect(runMutation).toHaveBeenNthCalledWith(
|
|
276
|
+
3,
|
|
277
|
+
components.agent.streams.abort,
|
|
278
|
+
{ streamId: "stream-1", reason: "delta failed" },
|
|
279
|
+
);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
test("surfaces pending-message cleanup failure after aborting the stream", async () => {
|
|
283
|
+
const pendingMessageFailure = new Error("pending message cleanup failed");
|
|
284
|
+
let resolveComponentAbort!: () => void;
|
|
285
|
+
const componentAborted = new Promise<void>((resolve) => {
|
|
286
|
+
resolveComponentAbort = resolve;
|
|
287
|
+
});
|
|
288
|
+
const runMutation = vi
|
|
289
|
+
.fn()
|
|
290
|
+
.mockResolvedValueOnce("stream-1")
|
|
291
|
+
.mockRejectedValueOnce(new Error("delta failed"))
|
|
292
|
+
.mockImplementationOnce(() => {
|
|
293
|
+
resolveComponentAbort();
|
|
294
|
+
return Promise.resolve();
|
|
295
|
+
});
|
|
296
|
+
const streamer = new DeltaStreamer<string>(
|
|
297
|
+
components.agent,
|
|
298
|
+
{ runMutation } as unknown as MutationCtx,
|
|
299
|
+
{
|
|
300
|
+
...defaultTestOptions,
|
|
301
|
+
onAsyncAbort: async () => {
|
|
302
|
+
throw pendingMessageFailure;
|
|
303
|
+
},
|
|
304
|
+
},
|
|
305
|
+
{ ...testMetadata, threadId },
|
|
306
|
+
);
|
|
307
|
+
const stream = {
|
|
308
|
+
async *[Symbol.asyncIterator]() {
|
|
309
|
+
yield "A";
|
|
310
|
+
await componentAborted;
|
|
311
|
+
},
|
|
312
|
+
} as unknown as Parameters<typeof streamer.consumeStream>[0];
|
|
313
|
+
|
|
314
|
+
await expect(streamer.consumeStream(stream)).rejects.toBe(
|
|
315
|
+
pendingMessageFailure,
|
|
316
|
+
);
|
|
317
|
+
|
|
318
|
+
expect(runMutation).toHaveBeenNthCalledWith(
|
|
319
|
+
3,
|
|
320
|
+
components.agent.streams.abort,
|
|
321
|
+
{ streamId: "stream-1", reason: "delta failed" },
|
|
322
|
+
);
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
test("finishes external abort cleanup when the active delta write fails", async () => {
|
|
326
|
+
const pendingMessageFailure = new Error("pending message cleanup failed");
|
|
327
|
+
let rejectDelta!: (error: Error) => void;
|
|
328
|
+
const deltaWrite = new Promise<never>((_, reject) => {
|
|
329
|
+
rejectDelta = reject;
|
|
330
|
+
});
|
|
331
|
+
const runMutation = vi
|
|
332
|
+
.fn()
|
|
333
|
+
.mockResolvedValueOnce("stream-1")
|
|
334
|
+
.mockImplementationOnce(() => deltaWrite)
|
|
335
|
+
.mockResolvedValueOnce(undefined);
|
|
336
|
+
const streamer = new DeltaStreamer<string>(
|
|
337
|
+
components.agent,
|
|
338
|
+
{ runMutation } as unknown as MutationCtx,
|
|
339
|
+
{
|
|
340
|
+
...defaultTestOptions,
|
|
341
|
+
onAsyncAbort: async () => {
|
|
342
|
+
throw pendingMessageFailure;
|
|
343
|
+
},
|
|
344
|
+
},
|
|
345
|
+
{ ...testMetadata, threadId },
|
|
346
|
+
);
|
|
347
|
+
|
|
348
|
+
await streamer.addParts(["A"]);
|
|
349
|
+
const failing = streamer.fail("external abort");
|
|
350
|
+
rejectDelta(new Error("delta failed"));
|
|
351
|
+
|
|
352
|
+
await expect(failing).rejects.toBe(pendingMessageFailure);
|
|
353
|
+
expect(runMutation).toHaveBeenNthCalledWith(
|
|
354
|
+
3,
|
|
355
|
+
components.agent.streams.abort,
|
|
356
|
+
{ streamId: "stream-1", reason: "external abort" },
|
|
357
|
+
);
|
|
358
|
+
});
|
|
185
359
|
// TODO: test fetching partial stream data - syncStreams w/ cursors
|
|
186
360
|
});
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
type StreamArgs,
|
|
18
18
|
type StreamDelta,
|
|
19
19
|
type StreamMessage,
|
|
20
|
-
} from "
|
|
20
|
+
} from "../../validators.js";
|
|
21
21
|
import type {
|
|
22
22
|
ActionCtx,
|
|
23
23
|
AgentComponent,
|
|
@@ -208,6 +208,7 @@ export class DeltaStreamer<T> {
|
|
|
208
208
|
#nextParts: T[] = [];
|
|
209
209
|
#latestWrite: number = 0;
|
|
210
210
|
#ongoingWrite: Promise<void> | undefined;
|
|
211
|
+
#abortPromise: Promise<void> | undefined;
|
|
211
212
|
#cursor: number = 0;
|
|
212
213
|
public abortController: AbortController;
|
|
213
214
|
// When true, the stream will be finished externally (e.g., atomically via addMessages)
|
|
@@ -243,34 +244,28 @@ export class DeltaStreamer<T> {
|
|
|
243
244
|
this.#nextParts = [];
|
|
244
245
|
this.abortController = new AbortController();
|
|
245
246
|
if (config.abortSignal) {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
await this.ctx.runMutation(this.component.streams.abort, {
|
|
259
|
-
streamId: this.streamId,
|
|
260
|
-
reason: "abortSignal",
|
|
261
|
-
});
|
|
262
|
-
}
|
|
263
|
-
} catch {
|
|
264
|
-
// Best-effort cleanup — the stream will be garbage-collected
|
|
265
|
-
// by the 10-minute timeout if this fails.
|
|
266
|
-
}
|
|
267
|
-
});
|
|
247
|
+
const abortFromSignal = () => {
|
|
248
|
+
void this.#abort("abortSignal").catch(() => {
|
|
249
|
+
// Best-effort cleanup — the stream timeout is the fallback.
|
|
250
|
+
});
|
|
251
|
+
};
|
|
252
|
+
if (config.abortSignal.aborted) {
|
|
253
|
+
abortFromSignal();
|
|
254
|
+
} else {
|
|
255
|
+
config.abortSignal.addEventListener("abort", abortFromSignal, {
|
|
256
|
+
once: true,
|
|
257
|
+
});
|
|
258
|
+
}
|
|
268
259
|
}
|
|
269
260
|
}
|
|
270
261
|
|
|
271
262
|
// Avoid race conditions by only creating once
|
|
272
263
|
#creatingStreamIdPromise: Promise<string> | undefined;
|
|
273
264
|
public async getStreamId() {
|
|
265
|
+
if (this.abortController.signal.aborted) {
|
|
266
|
+
await this.#abortPromise;
|
|
267
|
+
throw new Error("Cannot create a stream after it has been aborted");
|
|
268
|
+
}
|
|
274
269
|
if (!this.streamId) {
|
|
275
270
|
if (!this.#creatingStreamIdPromise) {
|
|
276
271
|
this.#creatingStreamIdPromise = this.ctx.runMutation(
|
|
@@ -310,9 +305,11 @@ export class DeltaStreamer<T> {
|
|
|
310
305
|
}
|
|
311
306
|
// Skip finish if it will be handled externally (atomically with message save)
|
|
312
307
|
// or if the stream was aborted (e.g., due to a failed delta write).
|
|
313
|
-
//
|
|
314
|
-
//
|
|
315
|
-
if (
|
|
308
|
+
// Abort cleanup owns the terminal component transition, so consumeStream
|
|
309
|
+
// must wait for it instead of also trying to finish the stream.
|
|
310
|
+
if (this.abortController.signal.aborted) {
|
|
311
|
+
await this.#waitForAbortCleanup();
|
|
312
|
+
} else if (!this.#finishedExternally) {
|
|
316
313
|
await this.finish();
|
|
317
314
|
}
|
|
318
315
|
}
|
|
@@ -343,28 +340,27 @@ export class DeltaStreamer<T> {
|
|
|
343
340
|
return;
|
|
344
341
|
}
|
|
345
342
|
this.#latestWrite = Date.now();
|
|
343
|
+
let success: boolean;
|
|
346
344
|
try {
|
|
347
|
-
|
|
345
|
+
success = await this.ctx.runMutation(
|
|
348
346
|
this.component.streams.addDelta,
|
|
349
347
|
delta,
|
|
350
348
|
);
|
|
351
|
-
if (!success) {
|
|
352
|
-
// An in-flight #sendDelta started before markFinishedExternally()
|
|
353
|
-
// will get `success === false` because the stream row is already
|
|
354
|
-
// "finished". That's a benign late-write miss, not a failure —
|
|
355
|
-
// don't convert it into an abort.
|
|
356
|
-
if (this.#finishedExternally) {
|
|
357
|
-
return;
|
|
358
|
-
}
|
|
359
|
-
await this.config.onAsyncAbort("async abort");
|
|
360
|
-
this.abortController.abort();
|
|
361
|
-
return;
|
|
362
|
-
}
|
|
363
349
|
} catch (e) {
|
|
364
|
-
await this
|
|
350
|
+
await this.#abortDelta(
|
|
365
351
|
e instanceof Error ? e.message : "unknown error",
|
|
366
352
|
);
|
|
367
|
-
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
if (!success) {
|
|
356
|
+
// An in-flight #sendDelta started before markFinishedExternally()
|
|
357
|
+
// will get `success === false` because the stream row is already
|
|
358
|
+
// "finished". That's a benign late-write miss, not a failure —
|
|
359
|
+
// don't convert it into an abort.
|
|
360
|
+
if (this.#finishedExternally) {
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
await this.#abortDelta("async abort");
|
|
368
364
|
return;
|
|
369
365
|
}
|
|
370
366
|
// Now that we've sent the delta, check if we need to send another one.
|
|
@@ -400,9 +396,17 @@ export class DeltaStreamer<T> {
|
|
|
400
396
|
if (!this.streamId) {
|
|
401
397
|
return;
|
|
402
398
|
}
|
|
403
|
-
|
|
399
|
+
try {
|
|
400
|
+
await this.#ongoingWrite;
|
|
401
|
+
} catch (error) {
|
|
402
|
+
if (this.abortController.signal.aborted) {
|
|
403
|
+
await this.#waitForAbortCleanup();
|
|
404
|
+
}
|
|
405
|
+
throw error;
|
|
406
|
+
}
|
|
404
407
|
await this.#sendDelta(); // #sendDelta checks aborted internally
|
|
405
408
|
if (this.abortController.signal.aborted) {
|
|
409
|
+
await this.#waitForAbortCleanup();
|
|
406
410
|
return;
|
|
407
411
|
}
|
|
408
412
|
await this.ctx.runMutation(this.component.streams.finish, {
|
|
@@ -411,18 +415,76 @@ export class DeltaStreamer<T> {
|
|
|
411
415
|
}
|
|
412
416
|
|
|
413
417
|
public async fail(reason: string) {
|
|
414
|
-
|
|
415
|
-
|
|
418
|
+
await this.#abort(reason);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
async #abortDelta(reason: string) {
|
|
422
|
+
let callbackFailure: { error: unknown } | undefined;
|
|
423
|
+
try {
|
|
424
|
+
await this.config.onAsyncAbort(reason);
|
|
425
|
+
} catch (error) {
|
|
426
|
+
callbackFailure = { error };
|
|
416
427
|
}
|
|
417
|
-
this
|
|
418
|
-
|
|
419
|
-
|
|
428
|
+
if (!this.#abortPromise) {
|
|
429
|
+
try {
|
|
430
|
+
await this.#abort(reason, false);
|
|
431
|
+
} catch {
|
|
432
|
+
// The stream timeout is the bounded cleanup fallback.
|
|
433
|
+
}
|
|
420
434
|
}
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
435
|
+
if (callbackFailure) throw callbackFailure.error;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
#abort(reason: string, waitForOngoingWrite = true): Promise<void> {
|
|
439
|
+
if (!this.#abortPromise) {
|
|
440
|
+
this.abortController.abort();
|
|
441
|
+
this.#abortPromise = this.#abortCreatedStream(
|
|
442
|
+
reason,
|
|
443
|
+
waitForOngoingWrite,
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
return this.#abortPromise;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
async #waitForAbortCleanup() {
|
|
450
|
+
let writeFailure: { error: unknown } | undefined;
|
|
451
|
+
try {
|
|
452
|
+
await this.#ongoingWrite;
|
|
453
|
+
} catch (error) {
|
|
454
|
+
writeFailure = { error };
|
|
455
|
+
}
|
|
456
|
+
try {
|
|
457
|
+
await this.#abortPromise;
|
|
458
|
+
} catch (error) {
|
|
459
|
+
writeFailure ??= { error };
|
|
460
|
+
}
|
|
461
|
+
if (writeFailure) throw writeFailure.error;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
async #abortCreatedStream(reason: string, waitForOngoingWrite: boolean) {
|
|
465
|
+
if (this.#creatingStreamIdPromise) {
|
|
466
|
+
this.streamId ??= await this.#creatingStreamIdPromise;
|
|
467
|
+
}
|
|
468
|
+
if (!this.streamId) return;
|
|
469
|
+
let writeFailure: { error: unknown } | undefined;
|
|
470
|
+
if (waitForOngoingWrite) {
|
|
471
|
+
try {
|
|
472
|
+
await this.#ongoingWrite;
|
|
473
|
+
} catch (error) {
|
|
474
|
+
writeFailure = { error };
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
let abortFailure: { error: unknown } | undefined;
|
|
478
|
+
try {
|
|
479
|
+
await this.ctx.runMutation(this.component.streams.abort, {
|
|
480
|
+
streamId: this.streamId,
|
|
481
|
+
reason,
|
|
482
|
+
});
|
|
483
|
+
} catch (error) {
|
|
484
|
+
abortFailure = { error };
|
|
485
|
+
}
|
|
486
|
+
if (writeFailure) throw writeFailure.error;
|
|
487
|
+
if (abortFailure) throw abortFailure.error;
|
|
426
488
|
}
|
|
427
489
|
}
|
|
428
490
|
|