ai 6.0.270 → 6.0.271

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.
@@ -6,8 +6,9 @@ import type { UIMessage } from '../ui/ui-messages';
6
6
  import { handleUIMessageStreamFinish } from './handle-ui-message-stream-finish';
7
7
  import type { InferUIMessageChunk } from './ui-message-chunks';
8
8
  import type { UIMessageStreamOnFinishCallback } from './ui-message-stream-on-finish-callback';
9
+ import type { UIMessageStreamOutcome } from './ui-message-stream-outcome';
9
10
  import type { UIMessageStreamOnStepFinishCallback } from './ui-message-stream-on-step-finish-callback';
10
- import type { UIMessageStreamWriter } from './ui-message-stream-writer';
11
+ import type { UIMessageStreamWriterWithOutcome } from './ui-message-stream-writer';
11
12
 
12
13
  /**
13
14
  * Creates a UI message stream that can be used to send messages to the client.
@@ -31,7 +32,7 @@ export function createUIMessageStream<UI_MESSAGE extends UIMessage>({
31
32
  generateId = generateIdFunc,
32
33
  }: {
33
34
  execute: (options: {
34
- writer: UIMessageStreamWriter<UI_MESSAGE>;
35
+ writer: UIMessageStreamWriterWithOutcome<UI_MESSAGE>;
35
36
  }) => Promise<void> | void;
36
37
  onError?: (error: unknown) => string;
37
38
 
@@ -55,6 +56,7 @@ export function createUIMessageStream<UI_MESSAGE extends UIMessage>({
55
56
  >;
56
57
 
57
58
  const ongoingStreamPromises: Promise<void>[] = [];
59
+ let outcome: UIMessageStreamOutcome = { status: 'unknown' };
58
60
 
59
61
  const stream = new ReadableStream({
60
62
  start(controllerArg) {
@@ -70,6 +72,42 @@ export function createUIMessageStream<UI_MESSAGE extends UIMessage>({
70
72
  }
71
73
  }
72
74
 
75
+ function setOutcome(newOutcome: UIMessageStreamOutcome) {
76
+ if (outcome.status === 'unknown' && newOutcome.status !== 'unknown') {
77
+ outcome = newOutcome;
78
+ }
79
+ }
80
+
81
+ function failOutcome(error: unknown) {
82
+ outcome = { status: 'failed', error };
83
+ }
84
+
85
+ function safeError(error: unknown) {
86
+ try {
87
+ controller.error(error);
88
+ } catch {
89
+ // suppress errors when the stream has been closed
90
+ }
91
+ }
92
+
93
+ function handleError(error: unknown) {
94
+ failOutcome(error);
95
+
96
+ let errorText: string;
97
+ try {
98
+ errorText = onError(error);
99
+ } catch (onErrorError) {
100
+ failOutcome(onErrorError);
101
+ safeError(onErrorError);
102
+ return;
103
+ }
104
+
105
+ safeEnqueue({
106
+ type: 'error',
107
+ errorText,
108
+ } as InferUIMessageChunk<UI_MESSAGE>);
109
+ }
110
+
73
111
  try {
74
112
  const result = execute({
75
113
  writer: {
@@ -86,13 +124,11 @@ export function createUIMessageStream<UI_MESSAGE extends UIMessage>({
86
124
  safeEnqueue(value);
87
125
  }
88
126
  })().catch(error => {
89
- safeEnqueue({
90
- type: 'error',
91
- errorText: onError(error),
92
- } as InferUIMessageChunk<UI_MESSAGE>);
127
+ handleError(error);
93
128
  }),
94
129
  );
95
130
  },
131
+ setOutcome,
96
132
  onError,
97
133
  },
98
134
  });
@@ -100,30 +136,23 @@ export function createUIMessageStream<UI_MESSAGE extends UIMessage>({
100
136
  if (result) {
101
137
  ongoingStreamPromises.push(
102
138
  result.catch(error => {
103
- safeEnqueue({
104
- type: 'error',
105
- errorText: onError(error),
106
- } as InferUIMessageChunk<UI_MESSAGE>);
139
+ handleError(error);
107
140
  }),
108
141
  );
109
142
  }
110
143
  } catch (error) {
111
- safeEnqueue({
112
- type: 'error',
113
- errorText: onError(error),
114
- } as InferUIMessageChunk<UI_MESSAGE>);
144
+ handleError(error);
115
145
  }
116
146
 
117
147
  // Wait until all ongoing streams are done. This approach enables merging
118
148
  // streams even after execute has returned, as long as there is still an
119
149
  // open merged stream. This is important to e.g. forward new streams and
120
150
  // from callbacks.
121
- const waitForStreams: Promise<void> = new Promise(async resolve => {
151
+ const waitForStreams: Promise<void> = (async () => {
122
152
  while (ongoingStreamPromises.length > 0) {
123
153
  await ongoingStreamPromises.shift();
124
154
  }
125
- resolve();
126
- });
155
+ })();
127
156
 
128
157
  waitForStreams.finally(() => {
129
158
  try {
@@ -140,5 +169,6 @@ export function createUIMessageStream<UI_MESSAGE extends UIMessage>({
140
169
  onStepFinish,
141
170
  onFinish,
142
171
  onError,
172
+ getOutcome: () => outcome,
143
173
  });
144
174
  }
@@ -8,6 +8,7 @@ import type { UIMessage } from '../ui/ui-messages';
8
8
  import type { ErrorHandler } from '../util/error-handler';
9
9
  import type { InferUIMessageChunk, UIMessageChunk } from './ui-message-chunks';
10
10
  import type { UIMessageStreamOnFinishCallback } from './ui-message-stream-on-finish-callback';
11
+ import type { UIMessageStreamOutcome } from './ui-message-stream-outcome';
11
12
  import type { UIMessageStreamOnStepFinishCallback } from './ui-message-stream-on-step-finish-callback';
12
13
 
13
14
  export function handleUIMessageStreamFinish<UI_MESSAGE extends UIMessage>({
@@ -17,6 +18,7 @@ export function handleUIMessageStreamFinish<UI_MESSAGE extends UIMessage>({
17
18
  onFinish,
18
19
  onError,
19
20
  stream,
21
+ getOutcome,
20
22
  }: {
21
23
  stream: ReadableStream<InferUIMessageChunk<UI_MESSAGE>>;
22
24
 
@@ -39,6 +41,11 @@ export function handleUIMessageStreamFinish<UI_MESSAGE extends UIMessage>({
39
41
  onStepFinish?: UIMessageStreamOnStepFinishCallback<UI_MESSAGE>;
40
42
 
41
43
  onFinish?: UIMessageStreamOnFinishCallback<UI_MESSAGE>;
44
+
45
+ /**
46
+ * Returns the operation-level outcome declared by the stream owner.
47
+ */
48
+ getOutcome?: () => UIMessageStreamOutcome;
42
49
  }): ReadableStream<InferUIMessageChunk<UI_MESSAGE>> {
43
50
  // last message is only relevant for assistant messages
44
51
  let lastMessage: UI_MESSAGE | undefined =
@@ -51,6 +58,13 @@ export function handleUIMessageStreamFinish<UI_MESSAGE extends UIMessage>({
51
58
  }
52
59
 
53
60
  let isAborted = false;
61
+ let hasProcessingFailure = false;
62
+ let processingError: unknown;
63
+
64
+ const recordProcessingFailure = (error: unknown) => {
65
+ hasProcessingFailure = true;
66
+ processingError = error;
67
+ };
54
68
 
55
69
  const idInjectedStream = stream.pipeThrough(
56
70
  new TransformStream<
@@ -58,21 +72,31 @@ export function handleUIMessageStreamFinish<UI_MESSAGE extends UIMessage>({
58
72
  InferUIMessageChunk<UI_MESSAGE>
59
73
  >({
60
74
  transform(chunk, controller) {
61
- // when there is no messageId in the start chunk,
62
- // but the user checked for persistence,
63
- // inject the messageId into the chunk
64
- if (chunk.type === 'start') {
65
- const startChunk = chunk as UIMessageChunk & { type: 'start' };
66
- if (startChunk.messageId == null && messageId != null) {
67
- startChunk.messageId = messageId;
75
+ try {
76
+ let outputChunk = chunk;
77
+
78
+ // when there is no messageId in the start chunk,
79
+ // but the user checked for persistence,
80
+ // inject the messageId into the chunk
81
+ if (chunk.type === 'start') {
82
+ const startChunk = chunk as UIMessageChunk & { type: 'start' };
83
+ if (startChunk.messageId == null && messageId != null) {
84
+ outputChunk = {
85
+ ...startChunk,
86
+ messageId,
87
+ } as InferUIMessageChunk<UI_MESSAGE>;
88
+ }
68
89
  }
69
- }
70
90
 
71
- if (chunk.type === 'abort') {
72
- isAborted = true;
73
- }
91
+ if (chunk.type === 'abort') {
92
+ isAborted = true;
93
+ }
74
94
 
75
- controller.enqueue(chunk);
95
+ controller.enqueue(outputChunk);
96
+ } catch (error) {
97
+ recordProcessingFailure(error);
98
+ throw error;
99
+ }
76
100
  },
77
101
  }),
78
102
  );
@@ -95,7 +119,12 @@ export function handleUIMessageStreamFinish<UI_MESSAGE extends UIMessage>({
95
119
  write: (options?: UIMessageStreamWriteOptions) => void;
96
120
  }) => Promise<void>,
97
121
  ) => {
98
- await job({ state, write: () => {} });
122
+ try {
123
+ await job({ state, write: () => {} });
124
+ } catch (error) {
125
+ recordProcessingFailure(error);
126
+ throw error;
127
+ }
99
128
  };
100
129
 
101
130
  let finishCalled = false;
@@ -107,9 +136,17 @@ export function handleUIMessageStreamFinish<UI_MESSAGE extends UIMessage>({
107
136
  finishCalled = true;
108
137
 
109
138
  const isContinuation = state.message.id === lastMessage?.id;
139
+ const declaredOutcome = getOutcome?.() ?? { status: 'unknown' };
140
+ const outcome: UIMessageStreamOutcome = hasProcessingFailure
141
+ ? { status: 'failed', error: processingError }
142
+ : declaredOutcome.status === 'unknown' && isAborted
143
+ ? { status: 'aborted' }
144
+ : declaredOutcome;
145
+
110
146
  await onFinish({
111
- isAborted,
147
+ isAborted: isAborted || outcome.status === 'aborted',
112
148
  isContinuation,
149
+ outcome,
113
150
  responseMessage: state.message as UI_MESSAGE,
114
151
  messages: [
115
152
  ...(isContinuation ? originalMessages.slice(0, -1) : originalMessages),
@@ -138,7 +175,13 @@ export function handleUIMessageStreamFinish<UI_MESSAGE extends UIMessage>({
138
175
  ] as UI_MESSAGE[],
139
176
  });
140
177
  } catch (error) {
141
- onError(error);
178
+ try {
179
+ onError(error);
180
+ } catch (onErrorError) {
181
+ recordProcessingFailure(onErrorError);
182
+ await callOnFinish();
183
+ throw onErrorError;
184
+ }
142
185
  }
143
186
  };
144
187
 
@@ -10,5 +10,9 @@ export {
10
10
  } from './ui-message-chunks';
11
11
  export { UI_MESSAGE_STREAM_HEADERS } from './ui-message-stream-headers';
12
12
  export type { UIMessageStreamOnFinishCallback } from './ui-message-stream-on-finish-callback';
13
+ export type { UIMessageStreamOutcome } from './ui-message-stream-outcome';
13
14
  export type { UIMessageStreamOnStepFinishCallback } from './ui-message-stream-on-step-finish-callback';
14
- export type { UIMessageStreamWriter } from './ui-message-stream-writer';
15
+ export type {
16
+ UIMessageStreamWriter,
17
+ UIMessageStreamWriterWithOutcome,
18
+ } from './ui-message-stream-writer';
@@ -1,5 +1,6 @@
1
1
  import type { FinishReason } from '../types/language-model';
2
2
  import type { UIMessage } from '../ui/ui-messages';
3
+ import type { UIMessageStreamOutcome } from './ui-message-stream-outcome';
3
4
 
4
5
  export type UIMessageStreamOnFinishCallback<UI_MESSAGE extends UIMessage> =
5
6
  (event: {
@@ -19,6 +20,12 @@ export type UIMessageStreamOnFinishCallback<UI_MESSAGE extends UIMessage> =
19
20
  */
20
21
  isAborted: boolean;
21
22
 
23
+ /**
24
+ * The operation-level outcome of the stream. Fatal stream-processing
25
+ * failures override outcomes declared by the stream owner.
26
+ */
27
+ outcome: UIMessageStreamOutcome;
28
+
22
29
  /**
23
30
  * The message that was sent to the client as a response
24
31
  * (including the original message if it was extended).
@@ -0,0 +1,12 @@
1
+ /**
2
+ * The operation-level outcome of a UI message stream.
3
+ *
4
+ * This is separate from model finish reasons and individual stream chunks.
5
+ * Fatal stream-processing failures override outcomes declared by the stream
6
+ * owner.
7
+ */
8
+ export type UIMessageStreamOutcome =
9
+ | { status: 'completed' }
10
+ | { status: 'failed'; error?: unknown }
11
+ | { status: 'aborted' }
12
+ | { status: 'unknown' };
@@ -1,6 +1,7 @@
1
1
  import type { UIMessage } from '../ui';
2
2
  import type { ErrorHandler } from '../util/error-handler';
3
3
  import type { InferUIMessageChunk } from './ui-message-chunks';
4
+ import type { UIMessageStreamOutcome } from './ui-message-stream-outcome';
4
5
 
5
6
  export interface UIMessageStreamWriter<
6
7
  UI_MESSAGE extends UIMessage = UIMessage,
@@ -22,3 +23,17 @@ export interface UIMessageStreamWriter<
22
23
  */
23
24
  onError: ErrorHandler | undefined;
24
25
  }
26
+
27
+ export interface UIMessageStreamWriterWithOutcome<
28
+ UI_MESSAGE extends UIMessage = UIMessage,
29
+ > extends UIMessageStreamWriter<UI_MESSAGE> {
30
+ /**
31
+ * Declares the operation-level outcome of the composed stream.
32
+ *
33
+ * The first outcome declared through this method is retained. Fatal
34
+ * execution, merge, error-handling, or downstream processing failures
35
+ * override declared outcomes. Declaring an outcome does not write a chunk or
36
+ * close the stream.
37
+ */
38
+ setOutcome(outcome: UIMessageStreamOutcome): void;
39
+ }