ai 7.0.82 → 7.0.84
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/CHANGELOG.md +24 -0
- package/dist/index.d.ts +77 -37
- package/dist/index.js +352 -129
- package/dist/index.js.map +1 -1
- package/dist/internal/index.js +1 -1
- package/docs/03-ai-sdk-harnesses/02-harness-agent.mdx +76 -1
- package/docs/03-ai-sdk-harnesses/04-skills.mdx +6 -2
- package/docs/07-reference/01-ai-sdk-core/02-stream-text.mdx +10 -3
- package/docs/07-reference/01-ai-sdk-core/80-smooth-stream.mdx +4 -2
- package/docs/07-reference/02-ai-sdk-ui/40-create-ui-message-stream.mdx +35 -6
- package/package.json +11 -12
- package/src/agent/create-agent-ui-stream.ts +2 -2
- package/src/agent/tool-loop-agent-settings.ts +9 -0
- package/src/generate-text/index.ts +2 -0
- package/src/generate-text/smooth-stream.ts +15 -3
- package/src/generate-text/stream-text.ts +111 -52
- package/src/ui/direct-chat-transport.ts +2 -2
- package/src/ui/last-assistant-message-is-complete-with-approval-responses.ts +1 -0
- package/src/ui/validate-ui-messages.ts +136 -55
- package/src/ui-message-stream/create-ui-message-stream.ts +47 -17
- package/src/ui-message-stream/handle-ui-message-stream-finish.ts +51 -14
- package/src/ui-message-stream/index.ts +5 -1
- package/src/ui-message-stream/to-ui-message-stream.ts +108 -26
- package/src/ui-message-stream/ui-message-stream-on-end-callback.ts +7 -0
- package/src/ui-message-stream/ui-message-stream-outcome.ts +12 -0
- package/src/ui-message-stream/ui-message-stream-writer.ts +15 -0
- package/src/util/create-stitchable-stream.ts +31 -1
|
@@ -7,6 +7,7 @@ import type { UIMessage } from '../ui/ui-messages';
|
|
|
7
7
|
import { getResponseUIMessageId } from './get-response-ui-message-id';
|
|
8
8
|
import { handleUIMessageStreamFinish } from './handle-ui-message-stream-finish';
|
|
9
9
|
import type { InferUIMessageChunk } from './ui-message-chunks';
|
|
10
|
+
import type { UIMessageStreamOutcome } from './ui-message-stream-outcome';
|
|
10
11
|
import { toUIMessageChunk } from './to-ui-message-chunk';
|
|
11
12
|
|
|
12
13
|
/**
|
|
@@ -37,6 +38,26 @@ export function toUIMessageStream<
|
|
|
37
38
|
} & UIMessageStreamOptions<UI_MESSAGE>): ReadableStream<
|
|
38
39
|
InferUIMessageChunk<UI_MESSAGE>
|
|
39
40
|
> {
|
|
41
|
+
let outcome: UIMessageStreamOutcome = { status: 'unknown' };
|
|
42
|
+
let hasFatalFailure = false;
|
|
43
|
+
|
|
44
|
+
const setSourceOutcome = (newOutcome: UIMessageStreamOutcome) => {
|
|
45
|
+
if (
|
|
46
|
+
!hasFatalFailure &&
|
|
47
|
+
outcome.status !== 'completed' &&
|
|
48
|
+
outcome.status !== 'aborted' &&
|
|
49
|
+
newOutcome.status !== 'unknown' &&
|
|
50
|
+
(outcome.status === 'unknown' || newOutcome.status !== 'failed')
|
|
51
|
+
) {
|
|
52
|
+
outcome = newOutcome;
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const failOutcome = (error: unknown) => {
|
|
57
|
+
hasFatalFailure = true;
|
|
58
|
+
outcome = { status: 'failed', error };
|
|
59
|
+
};
|
|
60
|
+
|
|
40
61
|
const responseMessageId =
|
|
41
62
|
generateMessageId != null
|
|
42
63
|
? getResponseUIMessageId({
|
|
@@ -45,37 +66,97 @@ export function toUIMessageStream<
|
|
|
45
66
|
})
|
|
46
67
|
: undefined;
|
|
47
68
|
|
|
48
|
-
const
|
|
69
|
+
const sourceReader = stream.getReader();
|
|
70
|
+
let sourceReaderReleased = false;
|
|
71
|
+
let sourceStreamCancelled = false;
|
|
72
|
+
|
|
73
|
+
const releaseSourceReader = () => {
|
|
74
|
+
if (!sourceReaderReleased) {
|
|
75
|
+
sourceReader.releaseLock();
|
|
76
|
+
sourceReaderReleased = true;
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const sourceStream = new ReadableStream<TextStreamPart<TOOLS>>({
|
|
81
|
+
async pull(controller) {
|
|
82
|
+
try {
|
|
83
|
+
const { done, value } = await sourceReader.read();
|
|
84
|
+
|
|
85
|
+
if (done) {
|
|
86
|
+
releaseSourceReader();
|
|
87
|
+
if (!sourceStreamCancelled) {
|
|
88
|
+
controller.close();
|
|
89
|
+
}
|
|
90
|
+
} else {
|
|
91
|
+
controller.enqueue(value);
|
|
92
|
+
}
|
|
93
|
+
} catch (error) {
|
|
94
|
+
releaseSourceReader();
|
|
95
|
+
if (!sourceStreamCancelled) {
|
|
96
|
+
failOutcome(error);
|
|
97
|
+
controller.error(error);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
async cancel(reason) {
|
|
103
|
+
sourceStreamCancelled = true;
|
|
104
|
+
if (sourceReaderReleased) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
await sourceReader.cancel(reason);
|
|
110
|
+
} finally {
|
|
111
|
+
releaseSourceReader();
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const uiMessageChunkStream = sourceStream.pipeThrough(
|
|
49
117
|
new TransformStream({
|
|
50
118
|
transform: async (part, controller) => {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const uiMessageChunk = toUIMessageChunk(part, {
|
|
54
|
-
tools,
|
|
55
|
-
sendReasoning,
|
|
56
|
-
sendSources,
|
|
57
|
-
sendStart,
|
|
58
|
-
sendFinish,
|
|
59
|
-
onError,
|
|
60
|
-
messageMetadata: messageMetadataValue,
|
|
61
|
-
responseMessageId,
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
if (uiMessageChunk != null) {
|
|
65
|
-
controller.enqueue(uiMessageChunk);
|
|
66
|
-
}
|
|
119
|
+
try {
|
|
120
|
+
const messageMetadataValue = messageMetadata?.({ part });
|
|
67
121
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
controller.enqueue({
|
|
76
|
-
type: 'message-metadata',
|
|
122
|
+
const uiMessageChunk = toUIMessageChunk(part, {
|
|
123
|
+
tools,
|
|
124
|
+
sendReasoning,
|
|
125
|
+
sendSources,
|
|
126
|
+
sendStart,
|
|
127
|
+
sendFinish,
|
|
128
|
+
onError,
|
|
77
129
|
messageMetadata: messageMetadataValue,
|
|
130
|
+
responseMessageId,
|
|
78
131
|
});
|
|
132
|
+
|
|
133
|
+
if (uiMessageChunk != null) {
|
|
134
|
+
controller.enqueue(uiMessageChunk);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// start and finish events already include metadata in the converted
|
|
138
|
+
// chunk; for other part types emit a separate message-metadata chunk
|
|
139
|
+
if (
|
|
140
|
+
messageMetadataValue != null &&
|
|
141
|
+
part.type !== 'start' &&
|
|
142
|
+
part.type !== 'finish'
|
|
143
|
+
) {
|
|
144
|
+
controller.enqueue({
|
|
145
|
+
type: 'message-metadata',
|
|
146
|
+
messageMetadata: messageMetadataValue,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (part.type === 'finish') {
|
|
151
|
+
setSourceOutcome({ status: 'completed' });
|
|
152
|
+
} else if (part.type === 'abort') {
|
|
153
|
+
setSourceOutcome({ status: 'aborted' });
|
|
154
|
+
} else if (part.type === 'error') {
|
|
155
|
+
setSourceOutcome({ status: 'failed', error: part.error });
|
|
156
|
+
}
|
|
157
|
+
} catch (error) {
|
|
158
|
+
failOutcome(error);
|
|
159
|
+
throw error;
|
|
79
160
|
}
|
|
80
161
|
},
|
|
81
162
|
}),
|
|
@@ -87,5 +168,6 @@ export function toUIMessageStream<
|
|
|
87
168
|
originalMessages,
|
|
88
169
|
onEnd: onEnd ?? onFinish,
|
|
89
170
|
onError,
|
|
171
|
+
getOutcome: () => outcome,
|
|
90
172
|
});
|
|
91
173
|
}
|
|
@@ -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 UIMessageStreamOnEndCallback<UI_MESSAGE extends UIMessage> =
|
|
5
6
|
(event: {
|
|
@@ -19,6 +20,12 @@ export type UIMessageStreamOnEndCallback<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
|
+
}
|
|
@@ -25,9 +25,14 @@ export function createStitchableStream<T>(): {
|
|
|
25
25
|
}> = [];
|
|
26
26
|
let controller: ReadableStreamDefaultController<T> | null = null;
|
|
27
27
|
let isClosed = false;
|
|
28
|
+
let isCancelled = false;
|
|
28
29
|
let waitForNewStream = createResolvablePromise<void>();
|
|
29
30
|
|
|
30
31
|
const terminate = () => {
|
|
32
|
+
if (isCancelled) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
31
36
|
isClosed = true;
|
|
32
37
|
waitForNewStream.resolve();
|
|
33
38
|
|
|
@@ -40,6 +45,10 @@ export function createStitchableStream<T>(): {
|
|
|
40
45
|
};
|
|
41
46
|
|
|
42
47
|
const processPull = async () => {
|
|
48
|
+
if (isCancelled) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
43
52
|
// Case 1: Outer stream is closed and no more inner streams
|
|
44
53
|
if (isClosed && innerStreams.length === 0) {
|
|
45
54
|
controller?.close();
|
|
@@ -59,6 +68,10 @@ export function createStitchableStream<T>(): {
|
|
|
59
68
|
try {
|
|
60
69
|
const { value, done } = await currentStream.reader.read();
|
|
61
70
|
|
|
71
|
+
if (isCancelled) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
62
75
|
if (done) {
|
|
63
76
|
// Case 3: Current inner stream is done
|
|
64
77
|
innerStreams.shift(); // Remove the finished stream
|
|
@@ -75,6 +88,10 @@ export function createStitchableStream<T>(): {
|
|
|
75
88
|
controller?.enqueue(value);
|
|
76
89
|
}
|
|
77
90
|
} catch (error) {
|
|
91
|
+
if (isCancelled) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
78
95
|
// Case 5: Current inner stream throws an error
|
|
79
96
|
currentStream.onError?.(error);
|
|
80
97
|
controller?.error(error);
|
|
@@ -90,12 +107,15 @@ export function createStitchableStream<T>(): {
|
|
|
90
107
|
},
|
|
91
108
|
pull: processPull,
|
|
92
109
|
async cancel() {
|
|
110
|
+
isCancelled = true;
|
|
111
|
+
isClosed = true;
|
|
112
|
+
waitForNewStream.resolve();
|
|
113
|
+
|
|
93
114
|
for (const { reader, onCancel } of innerStreams) {
|
|
94
115
|
onCancel?.();
|
|
95
116
|
await reader.cancel();
|
|
96
117
|
}
|
|
97
118
|
innerStreams = [];
|
|
98
|
-
isClosed = true;
|
|
99
119
|
},
|
|
100
120
|
}),
|
|
101
121
|
addStream: (
|
|
@@ -105,6 +125,12 @@ export function createStitchableStream<T>(): {
|
|
|
105
125
|
onCancel?: () => void;
|
|
106
126
|
},
|
|
107
127
|
) => {
|
|
128
|
+
if (isCancelled) {
|
|
129
|
+
callbacks?.onCancel?.();
|
|
130
|
+
void innerStream.cancel().catch(() => {});
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
108
134
|
if (isClosed) {
|
|
109
135
|
throw new Error('Cannot add inner stream: outer stream is closed');
|
|
110
136
|
}
|
|
@@ -121,6 +147,10 @@ export function createStitchableStream<T>(): {
|
|
|
121
147
|
* finish processing and then close the outer stream.
|
|
122
148
|
*/
|
|
123
149
|
close: () => {
|
|
150
|
+
if (isCancelled) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
124
154
|
isClosed = true;
|
|
125
155
|
waitForNewStream.resolve();
|
|
126
156
|
|